NOIp 2017 解题报告

A – 小凯的疑惑

送命规律题。当然也可以考虑用 exgcd 的性质,以下是感性理解:

考虑设这个数为\(x \equiv ya {\pmod b}\),展开就是:

\[ x = ya + kb, k \in [1, b – 1] \]

发现\(k \geq 0\)时都满足,那么反过来取\(-1\)就可以又满足最大又满足不满足条件。

// P3951.cpp
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    unsigned long long a, b, i;
    cin >> a >> b;
    cout << a * b - a - b;
    return 0;
}

B – 时间复杂度

刚开始我觉得这道题目非常的可怕,最后我还是自己写了出来(因为题目给的格式还是很好的,没有想象中的困难)。用计数器记录次数、是否零次循环即可。

// P3952.cpp
#include <bits/stdc++.h>

using namespace std;

char content[1010], opt[20];
int T, L, complexity, vis[300];

struct instruction
{
    char typ, var_name[3];
    bool tag, tag2;
    int x, y;
    instruction()
    {
        typ = 0, memset(var_name, 0, sizeof(var_name));
        tag = tag2 = false;
        x = y = 0;
    }
};
stack<instruction> st;

int read()
{
    int pos = 1, ret = 0;
    while (opt[pos] != 0)
        ret = ret * 10 + opt[pos] - '0', pos++;
    return ret;
}

int main()
{
    scanf("%d", &T);
    while (T--)
    {
        memset(vis, 0, sizeof(vis));
        // complexity reader;
        complexity = 0;
        memset(opt, 0, sizeof(opt));
        scanf("%d%s", &L, opt + 1);
        if (strlen(opt + 1) == 4)
            complexity = (opt[3] == '1') ? 0 : 1;
        else
            for (int i = 5, siz = strlen(opt + 1); i <= siz - 1; i++)
                complexity = complexity * 10 + (opt[i] - '0');
        // read the whole content;
        int mx_idx = 0;
        bool err_flag = false;
        while (!st.empty())
            st.pop();
        for (int i = 1, curt_complexity = 0, choke = 0; i <= L; i++)
        {
            instruction ist;
            ist.tag = false, ist.tag2 = false;
            scanf("%s", content + 1);
            ist.typ = content[1];
            if (ist.typ == 'F')
            {
                scanf("%s", ist.var_name + 1);
                // repeated name;
                err_flag |= (vis[ist.var_name[1]] > 0);
                vis[ist.var_name[1]]++;
                // read the condition;
                scanf("%s", opt + 1);
                if (opt[1] == 'n')
                    ist.x = -1;
                else
                    ist.x = read();

                scanf("%s", opt + 1);
                if (opt[1] == 'n')
                    ist.y = -1;
                else
                    ist.y = read();

                if (ist.x != -1 && ist.y == -1)
                {
                    // O(n);
                    if (choke == 0)
                        curt_complexity++, ist.tag2 = true;
                }
                else if (ist.x == -1 && ist.y != -1)
                    choke++, ist.tag = true;
                else if (ist.x != -1 && ist.y != -1 && ist.x > ist.y)
                    choke++, ist.tag = true;

                st.push(ist);
            }
            else if (ist.typ == 'E')
            {
                if (st.empty())
                    // nowhere to go;
                    err_flag = true;
                else
                {
                    instruction current_ist = st.top();
                    if (current_ist.tag)
                        choke--;
                    if (current_ist.tag2)
                        curt_complexity--;
                    vis[current_ist.var_name[1]]--;
                    st.pop();
                }
            }
            mx_idx = max(mx_idx, curt_complexity);
        }
        err_flag |= (!st.empty());
        if (err_flag)
        {
            puts("ERR");
            continue;
        }
        puts(mx_idx == complexity ? "Yes" : "No");
    }
    return 0;
}

C – 逛公园

策策主任逛机房。

这道题看上去超级难,但是最后用记忆化搜索的方式暴解起初让我意外,但是过了几秒之后真的觉得很妙。因为\(k < 50\),显然可以直接记忆化搜索状态进行转移(管他妈的转移顺序)。

设置状态\(dp[u][addition]\),其中\(addition\)代表的就是剩下的\(bonus\)成分,然后就可以开始暴力计数了。这个方法是真的好。

// P3953.cpp
#include <bits/stdc++.h>
#define ll long long
#define pr pair<ll, int>

using namespace std;

const int MAX_N = 100100, MAX_E = 200010;

int head[MAX_N], n, m, k, p, current, T;
ll dist[MAX_N], dp[MAX_N][60];
bool vis[MAX_N], inst[MAX_N][60];

struct edge
{
	int to, nxt;
	ll weight;
	bool tag;
} edges[MAX_E << 1];

void addpath(int src, int dst, ll weight, bool toggle)
{
	edges[current].to = dst, edges[current].nxt = head[src];
	edges[current].tag = toggle, edges[current].weight = weight;
	head[src] = current++;
}

void shortest_path()
{
	memset(dist, 0x3f, sizeof(dist));
	memset(vis, false, sizeof(vis));
	priority_queue<pr> pq;
	pq.push(make_pair(0, n)), dist[n] = 0;
	while (!pq.empty())
	{
		int u = pq.top().second;
		pq.pop();
		if (vis[u])
			continue;
		vis[u] = true;
		for (int i = head[u]; i != -1; i = edges[i].nxt)
			if (edges[i].tag == true && dist[edges[i].to] > dist[u] + edges[i].weight)
				dist[edges[i].to] = dist[u] + edges[i].weight, pq.push(make_pair(-dist[edges[i].to], edges[i].to));
	}
}

int dfs(int u, int addition)
{
	if (inst[u][addition])
		return -1;
	if (dp[u][addition])
		return dp[u][addition];
	inst[u][addition] = true;
	if (u == n)
		dp[u][addition] = 1;
	for (int i = head[u]; i != -1; i = edges[i].nxt)
		if (dist[edges[i].to] - dist[u] + edges[i].weight <= addition && edges[i].tag == true)
		{
			ll diff = dist[edges[i].to] - dist[u] + edges[i].weight;
			if (dfs(edges[i].to, addition - diff) == -1)
				return (dp[u][addition] = -1);
			dp[u][addition] = (1LL * dp[u][addition] + 1LL * dp[edges[i].to][addition - diff]) % p;
		}
	inst[u][addition] = false;
	return dp[u][addition];
}

int main()
{
	scanf("%d", &T);
	while (T--)
	{
		memset(head, -1, sizeof(head)), current = 0;
		memset(inst, 0, sizeof(inst)), memset(dp, 0, sizeof(dp));
		scanf("%d%d%d%d", &n, &m, &k, &p);
		for (int i = 1, u, v, w; i <= m; i++)
			scanf("%d%d%d", &u, &v, &w), addpath(u, v, w, false), addpath(v, u, w, true);
		shortest_path();
		for (int i = 0; i < current; i++)
			edges[i].tag ^= 1;
		printf("%d\n", dfs(1, k));
	}
	return 0;
}

D – 奶酪

sb 并查集搞定。

// P3958.cpp
#include <iostream>
#include <vector>
#include <cmath>

using namespace std;
#define ll long long

int n, h, r;

struct Point
{
    ll x, y, z;
    Point() {}
    Point(int x, int y, int z)
    {
        this->x = x;
        this->y = y;
        this->z = z;
    }
};

struct Sphere
{
    Point center;
    ll radius;
    bool upperColliding;
    bool lowerColliding;
    int index;

    Sphere() {}
    Sphere(Point c, int radius);
};

double getDist(Point a, Point b);
bool checkCollide(Sphere s1, Sphere s2);
bool checkLowerCollide(Sphere s);
bool checkUpperCollide(Sphere s);

Sphere::Sphere(Point c, int radius)
{
    center = c;
    this->radius = radius;
}

double getDist(Point a, Point b)
{
    return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2) + pow(a.z - b.z, 2));
}

bool checkUpperCollide(Sphere s)
{
    if (s.center.z + s.radius >= h)
        return true;
    return false;
}

bool checkLowerCollide(Sphere s)
{
    if (s.center.z - s.radius <= 0)
        return true;
    return false;
}

bool checkCollide(Sphere s1, Sphere s2)
{
    if (getDist(s1.center, s2.center) <= s1.radius + s2.radius)
        return true;
    return false;
}

struct Batch
{
    vector<Sphere> spheres;
    vector<Sphere> upper;
    vector<Sphere> lower;
    int mem[100100];

    void MakeHeap()
    {
        for (int i = 0; i < n + 2; i++)
            mem[i] = i;
    }

    void UnionElement(int p, int q)
    {
        int p_parent = FindElement(p);
        int q_element = FindElement(q);
        if (p_parent != q_element)
            mem[p_parent] = q_element;
    }

    int FindElement(int p)
    {
        if (mem[p] != p)
            mem[p] = FindElement(mem[p]);
        return mem[p];
    }

    bool allFlag = true;
    bool goFlag = false;

    void initialize()
    {
        cin >> n >> h >> r;
        for (int i = 0; i < n; i++)
        {
            ll x, y, z;
            cin >> x >> y >> z;
            Sphere sss = Sphere(Point(x, y, z), r);
            sss.index = i;
            sss.upperColliding = checkUpperCollide(sss);
            sss.lowerColliding = checkLowerCollide(sss);
            if (sss.upperColliding && sss.lowerColliding)
                goFlag = true;
            if (sss.upperColliding)
                allFlag = false, upper.push_back(sss);
            if (sss.lowerColliding)
                lower.push_back(sss);
            spheres.push_back(sss);
        }
        return;
    }

    void preprocess()
    {
        MakeHeap();
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
            {
                if (i == j)
                    continue;
                if (checkCollide(spheres[i], spheres[j]))
                    UnionElement(i, j);
            }
    }

    void Solve()
    {
        if (goFlag)
        {
            cout << "Yes" << endl;
            return;
        }
        if (allFlag)
        {
            cout << "No" << endl;
            return;
        }
        preprocess();
        for (int i = 0; i < upper.size(); i++)
            for (int j = 0; j < lower.size(); j++)
            {
                if (upper[i].index == lower[i].index)
                    continue;
                int resi = FindElement(upper[i].index), resj = FindElement(lower[j].index);
                if (resi != resj)
                    continue;
                cout << "Yes" << endl;
                return;
            }
        cout << "No" << endl;
    }
};

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        Batch b;
        b.initialize();
        b.Solve();
    }
    return 0;
}

E – 宝藏

想到了状态之后就算是 sb 题了。设置状态\(dp[stat][i]\),stat 就是已有的点集,\(i\)是最大树高。知道这个之后枚举子集就可以搞定了(因为最大树高为最糟情况,且这个情况会由状态压缩自动处理掉,请自行思考)。

// P3959.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 20, INF = 0x3f3f3f3f;

int dist[MAX_N][MAX_N], n, m, dp[1 << MAX_N][MAX_N], dist_mask[1 << MAX_N];

int main()
{
    memset(dist, 0x3f, sizeof(dist));
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v, w; i <= m; i++)
        scanf("%d%d%d", &u, &v, &w), u--, v--, dist[u][v] = dist[v][u] = min(dist[u][v], w);
    for (int i = 1; i < (1 << n); i++)
        for (int j = 0; j < n; j++)
            if (i & (1 << j))
            {
                dist[j][j] = 0;
                for (int k = 0; k < n; k++)
                    if (dist[j][k] != INF)
                        dist_mask[i] |= (1 << k);
            }
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 0; i < n; i++)
        dp[1 << i][0] = 0;
    for (int stat = 2; stat < (1 << n); stat++)
        for (int subset = stat - 1; subset; subset = (subset - 1) & stat)
            if ((dist_mask[subset] | stat) == dist_mask[subset])
            {
                int sum = 0, from = subset ^ stat;
                for (int k = 0; k < n; k++)
                    if (from & (1 << k))
                    {
                        int tmp = INF;
                        for (int idx = 0; idx < n; idx++)
                            if (subset & (1 << idx))
                                tmp = min(tmp, dist[idx][k]);
                        sum += tmp;
                    }
                for (int i = 1; i < n; i++)
                    if (dp[subset][i - 1] != INF)
                        dp[stat][i] = min(dp[stat][i], sum * i + dp[subset][i - 1]);
            }
    int ans = INF;
    for (int i = 0; i < n; i++)
        ans = min(ans, dp[(1 << n) - 1][i]);
    printf("%d", ans);
    return 0;
}

F – 列队

50 分退役题。

首先有一个性质:每一次出队的时候,只会影响当前列和当前行和右下角的格子。所以,不难想到,维护一堆线段树来记录每一列某个位置前有多少个人,这样我们可以用权值线段树那一套,来二分出那个位置(每次多了一个点,加一之后就会自动偏移,非常巧妙)。超过范围的就用 vector 存就好。

当然,即使是这样,我还是不会。

// P3960.cpp
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int MAX_N = 3e5 + 200, SEG = 1e7 + 200;

ll siz[SEG], n, m, q, ptot, upper;
int lson[SEG], rson[SEG], roots[SEG];
vector<ll> vecs[MAX_N];

ll atRank(ll k, int l, int r, int p)
{
    if (l == r)
        return l;
    ll mid = (l + r) >> 1, lsiz = mid - l + 1 - siz[lson[p]];
    if (k <= lsiz)
        return atRank(k, l, mid, lson[p]);
    else
        return atRank(k - lsiz, mid + 1, r, rson[p]);
}

int update(int qx, int l, int r, int p)
{
    if (p == 0)
        p = ++ptot;
    ++siz[p];
    if (l == r)
        return p;
    int mid = (l + r) >> 1;
    if (qx <= mid)
        lson[p] = update(qx, l, mid, lson[p]);
    else
        rson[p] = update(qx, mid + 1, r, rson[p]);
    return p;
}

ll query_col(int x, int y)
{
    ll tmp = atRank(y, 1, upper, roots[x]);
    roots[x] = update(tmp, 1, upper, roots[x]);
    return tmp < m ? 1LL * (x - 1) * m + tmp : vecs[x][tmp - m];
}

ll query_row(int x)
{
    ll tmp = atRank(x, 1, upper, roots[n + 1]);
    roots[n + 1] = update(tmp, 1, upper, roots[n + 1]);
    return tmp <= n ? 1LL * tmp * m : vecs[n + 1][tmp - n - 1];
}

int main()
{
    scanf("%lld%lld%lld", &n, &m, &q);
    upper = max(n, m) + q;
    while (q--)
    {
        int x, y;
        ll tmp;
        scanf("%d%d", &x, &y);
        if (y == m)
            tmp = query_row(x), vecs[n + 1].push_back(tmp), printf("%lld\n", tmp);
        else
        {
            tmp = query_col(x, y), vecs[n + 1].push_back(tmp);
            printf("%lld\n", tmp);
            tmp = query_row(x), vecs[x].push_back(tmp);
        }
    }
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *