CSP-S 2019 第二轮 DAY2 简单解析(含部分分代码)

CSP-S 2019第二轮第二试昨天上午正式结束了,赛后仔细拜读了DAY2的三个题目,难!但也是明年NOI选手不错的试炼机会,高分选手大概率也是明年NOI赛场上的选手。

 

第一题:Emiya 家今天的饭,比较难的动态规划

第二题:划分,动态规划,高精度,单调队列

第三题:树的重心,dfs序,线段树

 

以下代码均在oitiku测试,第一题Emiya 家今天的饭,O(n^2m)的算法仅得到84分,超时了4个点,理论上应该是可以通过的:)第二题划分,由于数据太大,写了高精度,写的不是太好,最后三个点在oitiku上MLE了,仅得到88分,之后再来改进:)第三题仅写了一下大致想法,往后再来补坑

 

 

CSP-S 2019 D2T1 Emiya 家今天的饭

思路:容斥,方案计数,对998, 244, 353取余数,已经在预示着这题是动态规划了。先考虑无限制情况下,显然是一个普及组难度的dp问题,然后考虑k/2的限制,我们可以从总的方案中减去不符合k/2限制的方案数即可。

代码如下:

 

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 210;
const int maxm = 2100;
const long long mod = 998244353;

long long g[maxn][maxn], f[maxn][maxn], a[maxn][maxm], line[maxn];
int n, m;

int main() {
	freopen("meal.in", "r", stdin);
	freopen("meal.out", "w", stdout);

	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j) {
			cin >> a[i][j];
			a[i][j] %= mod;
			line[i] = (line[i] + a[i][j]) % mod;
		}

	long long sum = 0;
	g[0][0] = 1; 
	for (int i = 1; i <= n; ++i)
		for (int j = 0; j <= i; ++j) {
			g[i][j] = g[i - 1][j];
			for (int k = 1; k <= m; ++k) {
				if (j > 0)
					g[i][j] += g[i - 1][j - 1] * a[i][k] % mod;
				g[i][j] %= mod;
			}
		}

	for (int i = 1; i <= n; ++i)
		sum = (sum + g[n][i]) % mod;

	for (int l = 1; l <= m; ++l) {
		memset(f, 0, sizeof(f));

		// 差值可能为负数,偏移量为n + 1
		f[0][n + 1] = 1;
		for (int i = 1; i <= n; ++i)
			for (int j = n + 1 - i; j <= i + n + 1; ++j) {
				f[i][j] = f[i - 1][j];
				f[i][j] += (f[i - 1][j - 1] * a[i][l]) % mod;
				f[i][j] += (f[i - 1][j + 1] * ((line[i] - a[i][l] + mod) % mod)) % mod;
				f[i][j] %= mod;
			}

		for (int i = 1; i <= n; ++i) {
			sum = (sum - f[n][i + n + 1]) % mod;
			if (sum < 0)
				sum += mod;
		}

	}
	cout << sum << endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}





 

 

CSP-S 2019 D2T2 划分

思路:根据数据规模显然是要一个O(n)的算法,粗略的就是先贪心,然后典型的序列型动态规划。dp一下n^2,必须使用单调队列优化至O(n)。最后由于数据太大,需要用高精度或者别的取巧的方法优化一下,我们这里使用传统的高精度来写一下:)

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 4e7, maxm = 1e5;

int n, type, x, y, z, m;
int a[maxn], p[maxm], l[maxm], r[maxm], b[maxn + 1], q[maxn + 1], head, tail;
long long sum[maxn + 1];

struct BigInteger {
    
    int len, data[50];
    
    BigInteger(string& s) {
        memset(data, 0, sizeof(data));
        len = s.size();
        for (int i = 0; i < len; ++i)
            data[i] = s[len - i - 1] - '0';
    }
    
    void set(long long num) {
        len = 0;
        memset(data, 0, sizeof(data));
        while (num > 0) {
            data[len] = num % 10;
            len ++;
            num /= 10;
        }
        len ++;
    }

    BigInteger() {
        len = 0;
        memset(data, 0, sizeof(data));
    }
    
    BigInteger operator + (const BigInteger &b) {
        BigInteger c;

        c.len = 0;
        while(c.len <= max(len, b.len) + 1) {
            c.data[c.len] += data[c.len] + b.data[c.len];
            c.data[c.len + 1] += c.data[c.len] / 10;
            c.data[c.len] %= 10;
            c.len ++;
        }
        while (c.len > 0 && c.data[c.len] == 0)
            c.len --;

        c.len++;
        
        return c;
    }
    
    BigInteger operator * (const BigInteger &b) {
        BigInteger c;

        c.len = len + b.len - 1;
        for (int i = 0; i < len; ++i)
            for (int j = 0; j < b.len; ++j)
                c.data[i + j] += data[i] * b.data[j];
        
        for (int i = 1; i < c.len; ++i) {
            c.data[i] += c.data[i - 1] / 10;
            c.data[i - 1] %= 10;
        }
        
        while (c.data[c.len - 1] >= 10) {
            c.data[c.len] = c.data[c.len - 1] / 10;
            c.data[c.len - 1] %= 10;
            c.len += 1;
        }

        return c;
    }

    string toString() {
        string s = "";
        for (int i = len - 1; i >= 0; --i)
            s += (char)(data[i] + '0');
        return s;
    }
};

int main() {
    freopen("partition.in", "r", stdin);
    freopen("partition.out", "w", stdout);

    cin >> n >> type;
    if (type == 0) {
        for (int i = 0; i < n; ++i)
            cin >> a[i];
    } else {
        cin >> x >> y >> z >> a[0] >> a[1] >> m;
        for (int i = 2; i < n; ++i)
            a[i] = (1LL * x * a[i - 1] + 1LL * y * a[i - 2] + z) & 0x3fffffff;
        for (int i = 0; i < m; ++i)
            cin >> p[i] >> l[i] >> r[i];
        for (int i = 0, j = 0; i < n; ++i) {
            if (i == p[j]) ++j;
            a[i] = a[i] % (r[j] - l[j] + 1) + l[j];
        }
    }

    for (int i = 0; i < n; ++i)
        sum[i + 1] = sum[i] + a[i];

    q[tail++] = 0;
    for (int i = 0; i < n; ++i) {
        while (head + 1 < tail && 2 * sum[q[head + 1]] - sum[b[q[head + 1]]] <= sum[i + 1])
            ++head;
        b[i + 1] = q[head];
        while (head < tail && 2 * sum[i + 1] - sum[b[i + 1]] <= 2 * sum[q[tail - 1]] - sum[b[q[tail - 1]]])
            --tail;
        q[tail++] = i + 1;
    }

    BigInteger ans, tmp;
    for (int i = n; i > 0; i = b[i]) {
        tmp.set((long long)(sum[i] - sum[b[i]]));
        tmp = tmp * tmp;
        ans = ans + tmp;
    }
    cout << ans.toString() << endl;
    fclose(stdin);
    fclose(stdout);
    return 0;
}



 

CSP-S D2T3 树的重心

思考:

https://codeforces.com/problemset/problem/686/D,恰好比赛前跟同学们也讨论过子树重心的问题。

 

树的问题的确可以深挖很多有趣的性质,这也是我比较喜欢树上的问题的一些原因。这可能也是为什么树的题目出那么多的原因之一吧。逆向思维一下,如果这点是树的重心,那么一定是删除size最大的子树里面的边。本质dfs序,然后用线段树维护即可。

 

 

2019赛季告一段落,高潮已过,停课冲刺的同学们该调整心情回归课堂了,冲刺明年NOI的同学,革命尚未成功,同志仍需努力!

 

每一位同学都值得为这次CSP-J/S赛写一个总结,心得,感悟,亦或者是回顾自己的学习C++/算法竞赛、OI的历程。

 

灿烂的明天正在等着你们!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章