2022 icpc 新疆省赛

以下题目按照我个人看法从易到难

注意我个人习惯性 define int long long!

K

显然题意就是求从 \(l\) 加到 \(r\)

ll calc(ll x){
	return x * (x + 1) / 2;
}

void solve(){
	int l, r;
	cin >> l >> r;
	cout << calc(r) - calc(l - 1);
}

B

要使得差异总和最小,我们只需要每一位贪心考虑,\(0\) 多取 \(0\) , \(1\) 多取 \(1\) 就行,一样多优先取 \(0\)

const int N = 1e3 + 10;

char s[N][N];
char res[N];
int n, m;

void solve(){
	cin >> n >> m;
	for(int i = 1 ; i <= n ; i ++)
		cin >> (s[i] + 1);
	for(int j = 1 ; j <= m ; j ++) {
		int a = 0, b = 0;
		for(int i = 1 ; i <= n ; i ++)
			if(s[i][j] == '0') a ++;
			else b ++;
		if(a >= b) res[j] = '0';
		else res[j] = '1';
	}
	cout << (res + 1);
}

G

赤裸裸的背包,只不过有两个包,价值二选一

const int N = 510;

int f[N][N];
int v[N], w1[N], w2[N];
int n, m, k;


void solve(){
	cin >> n >> m >> k;
	for(int i = 1 ; i <= n ; i ++) 
		cin >> v[i] >> w1[i] >> w2[i];
	for(int i = 1 ; i <= n ; i ++) {
		for(int j = m ; j >= 0 ; j --)
			for(int l = k ; l >= 0 ; l --){
				int a = 0, b = 0;
				// 如果可以放第一个背包 
				if(j >= v[i]) a = f[j - v[i]][l] + w1[i];
				// 如果可以放第二个背包 
				if(l >= v[i]) b = f[j][l - v[i]] + w2[i];
				int c = max(a, b);
				f[j][l] = max(f[j][l], c);
			}
	}
	cout << f[m][k] << "\n";
}

A

典型的树形dp,注意食物链的数目是叶子结点的个数

const int N = 2e6 + 10, mod = 32416190071;
int pre[N];
int h[N], e[N], ne[N], w[N], idx;
int f[N];
int link;
bool st[N];
int n;

void add(int a, int b, int c){
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}

void dfs(int u) {
	if(h[u] == -1) link ++;
	f[u] = pre[u] % mod;
	for(int i = h[u] ; ~i ; i = ne[i]) {
		int j = e[i], c = w[i];
		dfs(j);
		f[u] = (f[u] + f[j] * c) % mod;
	}
}

void solve(){
	cin >> n;
	memset(h, -1, sizeof h);
	for(int i = 1 ; i < n ; i ++) {
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c), st[b] = true;
	}	
	for(int i = 1 ; i <= n ; i ++) cin >> pre[i];
	int root;
	for(int i = 1 ; i <= n ; i ++)
		if(!st[i]) {
			root = i;
			break;
		}
	dfs(root);
	cout << link << "\n";
	cout << f[root] % mod << "\n";
}

J

J题题目强调了,询问的区间起点必须是 \(a[L]\)
考虑到 \(gcd\) 只会单调递减,故区间查询 \(gcd\) + 二分即可
使用线段树好像被卡常了,查询的时候多了个 \(log\) ...

const int N = 3e5 + 10, M = 21, mod = 32416190071;

int f[N][M];
int a[N];
int n, m;

void init(){
	for(int j = 0 ; j < M ; j ++)
		for(int i = 1 ; i + (1 << j) - 1 <= n ; i ++) {
			if(!j) f[i][j] = a[i];
			else {
				f[i][j] = __gcd(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
			}
		}
}

int query(int l, int r){
	int k = log(r - l + 1) / log(2);
	return __gcd(f[l][k], f[r - (1 << k) + 1][k]);
}

void solve(){
	cin >> n >> m;
	for(int i = 1 ; i <= n ; i ++) cin >> a[i];
	init();
	while(m --){
		int L, R, l, r;
		cin >> L >> R;
		l = L, r = R;
		while(l < r){
			int mid = l + r >> 1;
			int c = query(L, mid);
			if(c > mid - L + 1) l = mid + 1;
			else r = mid;
		}
		bool flag = false;
		if(query(L, r) == r - L + 1) flag = true;
		if(flag) cout << "YES\n";
		else cout << "NO\n";
	}
}

C

C题有点阴间,需要求和 \([L, R]\) 里每个数的最大开方次数(开方结果是整数)
我们首先转化为求 \([1, n]\) ,这样再用前缀和的思想减去就行
那么怎么求和 \([1, n]\) 呢?
首先我们求出\(n\)最多能够开方多少次,由于 \(L >= 2\)
我们统一认为 \(1\) 的贡献是 \(1\) 就行,那么整数我们最少以 \(2\) 为底
\(log(n) / log(2)\) 就能求出以二为底的最大次幂
我们用 \(cnt[i]\) 表示从 \(2\)\(n\) 最多能开方 \(i\) 次的数的个数
首先每个数都至少能开一次幂,还是它本身
然后我们从大到小枚举次幂进行开方,就能得到 \([1, n]\) 有多少数可以满足开这个次幂
或者反过来认为乘这个次方后仍然在区间范围内,注意要减去 \(1\) ,因为我们统一认为 \(1\) 的贡献是 \(1\) ,那么它只能开一次幂
但是注意,上面的写法是有重复的,因为有的数乘方再乘方它还在范围内
因此需要进行对应倍数的去重,如下代码所示
最后答案再加上 \(n\) 就行,因为上面的做法是枚举到了至少开平方

const int N = 110;

int cnt[N];

int calc(int x){
	if(x < 2) return 1;
	memset(cnt, 0, sizeof cnt);
	int maxd = log(x) / log(2);
	for(int j = maxd ; j >= 2 ; j --){
		cnt[j] = pow(x, 1.0 / j) - 1;
		for(int k = 2 ; k * j <= maxd ; k ++)
			cnt[j] -= cnt[k * j];
	}
	int res = 0;
	for(int i = 2 ; i <= maxd ; i ++)
		res += cnt[i] * (i - 1);
	return res + x;
}

void solve(){
	int l, r;
	while(cin >> l >> r, l || r) {
		cout << calc(r) - calc(l - 1) << "\n";
	}
}

H

这个题本身考察的东西不难,最大异或对。
但是如何转化问题是难点。
首先我们搞一个数组 \(B = A + k\)
然后一起求前缀异或和
然后我们开始 \(i\)\(0\)\(n\) 这样操作:
往字典树插入:\(a[n]\) \(XOR\) \(a[i]\) \(XOR\) \(b[i]\)
这相当于做了什么呢,插入了一段我浇了 \([1, i]\) 这段花的情况,(\(i = 0\) 相当于不浇花)
然后我们每次查询 \(a[i]\) \(XOR\) \(b[i]\) 此时在字典树里的最大异或对,相当于就是
可以在当前的情况下,任选一段 \([1, x], x <= i\) 取消浇水
那么利用这个做法,做完整个循环,这样就相当于能枚举到所有的浇水情况了

const int N = 1e5 + 10, M = N * 35;
 
int tr[M][2], idx;
int a[N], b[N];
int n, k;
 
void insert(int x){
    int p = 0;
    for(int i = 31 ; i >= 0 ; i --) {
        int c = (x >> i) & 1;
        if(!tr[p][c]) tr[p][c] = ++ idx;
        p = tr[p][c];
    }
}
 
int query(int x){
    int res = 0, p = 0;
    for(int i = 31 ; i >= 0 ; i --) {
        int c = ((x >> i) & 1) ^ 1;
        if(tr[p][c]) res += (1 << i), p = tr[p][c];
        else if(tr[p][c ^ 1]) p = tr[p][c ^ 1];
        else break;
    }   
    return res;
}
 
void solve(){
    cin >> n >> k;
    for(int i = 1 ; i <= n ; i ++) cin >> a[i], b[i] = a[i] + k;
    for(int i = 1 ; i <= n ; i ++) a[i] ^= a[i - 1], b[i] ^= b[i - 1];
    int res = 0;
    for(int i = 0 ; i <= n ; i ++){
        int x = a[n] ^ a[i] ^ b[i];
        insert(x);
        res = max(res, query(a[i] ^ b[i]));
    }
    cout << res << "\n";
}

D

不难发现:对于任意 \(n\) 个长方形,记第 \(i\) 个长方形的高度为 \(a_i\),则这 \(n\) 个长方形顺次拼接组成的多边形的周长为:

\[2 \ast n + a_1 + a_n + \sum\limits_{1 \leq i < n} |a_i - a_{i + 1}| \]

答案涉及到了相邻两个长方形的高度差

考虑状压 dp。

\(F_{S, x}\) 表示:由集合 \(S\) 中的所有长方形组成的多边形中(钦定第 \(x\) 块为整个多边形的最后一块),组成的多边形的最大周长是多少(不考虑上面那个 \(2 \ast n\))。

\(G_{S, x}\) 表示:由集合 \(S\) 中的所有长方形组成的多边形中(钦定第 \(x\) 块为整个多边形的最后一块),组成的多边形是最大周长的方案数是多少。

当集合 \(S\) 只有 \(x\) 这一个元素的时候有 \(F_{S, x} = a_x\)\(G_{S, x} = 1\)

否则,枚举集合 \(S\) 中的所有长方形组成的多边形中倒数第二块的长方形 \(y\),记集合 \(S\) 除去元素 \(x\) 后的集合为 \(T\),则有状态转移方程:

\[F_{S, x} = \max \{ F_{T, y} + |a_x - a_y| \} \]

\(G\) 数组在转移的过程中稍微更新一下即可,遵循大于就继承、等于就累加的原则,相信大家都会。

目标:设集合 \(S\) 包含了所有的长方体,则最长的周长即为 \(\max\{ F_{S, x} + a_x + 2 \ast n \}\),方案数也像求 \(G\) 数组那样累加一下即可。

单组数据的时间复杂度为 \(\mathcal{O}(2^n \ast n^2)\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> 

using namespace std;

int lowbit(int x) {
	return x & -x;
}

const int N = 16;

int n;

int a[N];

long long f[1 << N][N];
long long g[1 << N][N];

void work() {
	for (int i = 0; i < n; i ++)
		scanf("%d", &a[i]);

	memset(f, 0, sizeof(f));
	memset(g, 0, sizeof(g));

	for (int S = 0; S < (1 << n); S ++) {	// 枚举集合 S 
		for (int x = 0; x < n; x ++)
			if (S >> x & 1) {
				if (lowbit(S) == S) {		// 集合 S 中只有一个元素 
					f[S][x] = a[x], g[S][x] = 1;
				} else {
					for (int y = 0; y < n; y ++)
						if (S >> y & 1) {
							if (x == y) continue;
							if (f[S ^ (1 << x)][y] + abs(a[x] - a[y]) > f[S][x])
								f[S][x] = f[S ^ (1 << x)][y] + abs(a[x] - a[y]),
								g[S][x] = g[S ^ (1 << x)][y]; 
							else if (f[S ^ (1 << x)][y] + abs(a[x] - a[y]) == f[S][x])
								g[S][x] += g[S ^ (1 << x)][y];
						}
				}
			}
	}

	long long cur = 0;
	long long cnt = 0;

	for (int x = 0; x < n; x ++)
		if (f[(1 << n) - 1][x] + a[x] > cur)
			cur = f[(1 << n) - 1][x] + a[x],
			cnt = g[(1 << n) - 1][x];
		else if (f[(1 << n) - 1][x] + a[x] == cur)
			cnt += g[(1 << n) - 1][x];

	cur += 2 * n;

	printf("%lld %lld\n", cur, cnt);
}

int main() {
	while (scanf("%d", &n), n)    work();

	return 0;
}

E

所求即为包含\(1\)号点且代价和小于等于\(M\)的,价值最大的连通块

容易根据树上背包得到一个\(O(nm^2)\)的DP

考虑一定要包含1号点的限制,我们以\(1\)号点为根,求出每个点的DFS序,按照DFS序的顺序转 移。

\(f_(i,j)\) 表示考虑到DFS序上的第i个点且连通块内代价和为j的最大价值,\(size_i\)表示DFS序上的第\(i\)个点的子树大小,转移分为两种:

不将第\(i+1\)个点加入连通块,\(i+1\)子树内的所有点也一定不会被加入连通块, 又由于\(i+1\)子树在DFS序上是一段连续的区间,可以得到转移 \(f_(i+size_i,j)←f_(i,j)\)

将第\(i+1\)个点加入连通块,显然有\(f_(i+1,j+P_(i+1) )←f_(i,j)+V_(i+1)\)

复杂度\(O(nm)\)

#include <bits/stdc++.h>
using namespace std;


struct node {
    int to;
    int next;
} e[4333];

int head[4333], cnt;

void add(int u, int v) {
    e[++cnt] = (node){ v, head[u] };
    head[u] = cnt;
}

int v[2333], p[2333];
int f[2333][2333];
int id[2333];
int sz[2333];
int n, m;
int ans;

inline int max(int a, int b) { return a > b ? a : b; }

void dfs(int u, int fa) {
    id[++id[0]] = u;
    sz[u] = 1;
    for (register int i(head[u]); i; i = e[i].next) {
        int v = e[i].to;
        if (v == fa)
            continue;
        dfs(v, u);
        sz[u] += sz[v];
    }
}

int main() {
    cin >> n, cin >> m;
    memset(f, -0x3f, sizeof(f));
    for (register int i(1); i <= n; ++i) {
        cin >> v[i];
        cin >> p[i];
    }
    for (register int i(1); i < n; ++i) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, 0);
    if (p[1] <= m)
        f[1][p[1]] = v[1];
    for (register int i(1); i < n; ++i)
        for (register int j(m); j >= 0; --j) {
            if (f[id[i]][j] != f[0][0]) {
                if (j + p[id[i + 1]] <= m)
                    f[id[i + 1]][j + p[id[i + 1]]] =
                        max(f[id[i + 1]][j + p[id[i + 1]]], f[id[i]][j] + v[id[i + 1]]);
                if (sz[id[i]])
                    f[id[i + sz[id[i + 1]]]][j] =
                        max(f[id[i + sz[id[i + 1]]]][j], f[id[i]][j]);
            }
        }
    for (register int i(1); i <= m; ++i) ans = max(ans, f[id[n]][i]);
   	cout << ans << "\n";
    return 0;
}

F

先二分答案。设我们要判定 \(\mathrm{mid}\) 次攻击下是否消灭所有妖怪。

同一轮一个妖怪不能被攻击多次,等价于每个妖怪都不受到超过 \(\mathrm{mid}\) 次伤害。

我们有了一个 DP 模型:

\(f(i,j,k)\) 表示前 \(i\) 个妖怪,用了 \(j\) 次伤害为 \(9\) 的攻击和 \(k\) 次伤害为 \(3\) 的攻击,至少要用多少次伤害为 \(1\) 的攻击。

转移有以下两个:

  1. 使用 \(h\) 次伤害为 \(9\) 的攻击和 \(r\) 次伤害为 \(3\) 的攻击无法消灭第 \(i\) 个妖怪。

    \[f(i,j,k) \leftarrow f(i-1,j-h,k-r)+\mathrm{blood}_i-9\times h-3\times r \]

    上式必须满足 \(h+r+\mathrm{blood}_i-9\times h-3\times r≤mid\)(即第 \(i\) 个妖怪不受到超过 \(\mathrm{mid}\) 次伤害)且 \(h≤j,r≤k\)

  2. 使用 \(h\) 次伤害为 \(9\) 的攻击和 \(r\) 次伤害为 \(3\) 的攻击恰好消灭第 \(i\) 个妖怪。
    「恰好消灭」的定义:\(9\times h+3\times r≥\mathrm{blood}_i\), 且 \((h-1,r)(h>0)\)\((h,r-1)(r>0)\) 都无法将其消灭。

    \[f(i,j,k)\leftarrow f(i-1,j-h,k-r) \]

    上式必须满足 \(h+r≤\mathrm{mid}\)\(h≤j,r≤k\)。当枚举一个 \(h\)\(r\) 就能确定。

判定:如果 \(\min\{f(m,j,k)\} ≤ \mathrm{mid}\),则 \(\mathrm{mid}\) 次攻击下可以消灭所有妖怪,否则不行。

const int N = 110, M = 2 * N, INF = 0x3f3f3f3f, mod = 32416190071;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int P = 131;
 
int bld[N];
int n;
int f[N][N][N]; 
// f[i][j][k] 前 i 个, j 次 9, k 次 3 还需多少 1 
 
void mind(int &x, int y){
    x = min(x, y);
}
 
bool check(int x){
    for(int i = 1 ; i <= n ; i ++)
        for(int j = 0 ; j <= x ; j ++)
            for(int k = 0 ; k <= x ; k ++)
                f[i][j][k] = INF;
     
    for(int i = 1 ; i <= n ; i ++)
        for(int j = 0 ; j <= x ; j ++)
            for(int k = 0 ; k <= x ; k ++)
                for(int l = 0 ; l <= j ; l ++)
                    for(int m = 0 ; m <= k ; m ++) {
                        int c = 9 * l + 3 * m;
                        if(bld[i] - c > 0) {
                            if(l + m + bld[i] - c <= x) 
                                mind(f[i][j][k], f[i - 1][j - l][k - m] + bld[i] - c);
                        } else {
                            if(l + m <= x) {
                                mind(f[i][j][k], f[i - 1][j - l][k - m]);
                                break;
                            }
                        }
                    }
     
    int res = INF;
    for(int i = 0 ; i <= x ; i ++)
        for(int j = 0 ; j <= x ; j ++)
            res = min(res, f[n][i][j]);
    return res <= x;
}
 
void solve(){
    cin >> n;
    for(int i = 1 ; i <= n ; i ++) cin >>  bld[i];
    int l = 1, r = 100;
    while(l < r){
        int mid = l + r >> 1;
        if(!check(mid)) l = mid + 1;
        else r = mid;
    }
    cout << r << "\n";
}
 
 
signed main() {
    IOS;
//  init();
    int T = 1;
    cin >> T;
    while(T --) {
        solve(); 
    }
    return 0;
}
 
/*
 
*/

I

先对\(a[i]\) % \(mod\) 之后离散化的值为\(b[i]\)

然后将下标存到vector \(g[b[i]]\)

从g[1]遍历到g[n],如果遇到g[i]的个数大于x的用FFT, 否则就直接\(n^2\)暴力

一次FFT的时间为\(T = 2 * M logM(M = 2 * MAXN) = 72 * 10^5\)

设当一组数据的大小 大于 \(x\)时用\(fft\)跑,否则 \(n^2\)暴力跑

因此 \(x^2 * (n/x) = =(n/x)*T\)

那么\(x = 2683\)

所以在处理每组数据的时候判断一下大小

#include<bits/stdc++.h> 

using namespace std;

typedef long long LL;

const int N = 3e5 + 10, mod = 1e9 + 7;
const double PI = acos(-1);

int n, cnt;
LL w[N], arr[N];
LL res = 1;
vector<int> g[N];

struct Complex {
	double x, y;
	Complex operator+ (const Complex& t) const {
		return {x + t.x, y + t.y};
	}
	Complex operator- (const Complex& t) const {
		return {x - t.x, y - t.y};
	}
	Complex operator* (const Complex& t) const {
		return {x * t.x - y * t.y, x * t.y + y * t.x};
	}
} a[N];

int rev[N], bit, tot;  // tot = 1 << bit

void FFT(Complex a[], int inv) {
	for (int i = 0; i < tot; i ++ ) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
	for (int i = 0; i < tot; i ++ )
		if (i < rev[i]) swap(a[i], a[rev[i]]);

	for (int mid = 1; mid < tot; mid <<= 1) {
		auto w1 = Complex({cos(PI / mid), inv * sin(PI / mid)});
		for (int i = 0; i < tot; i += mid * 2) {
			auto wk = Complex({1, 0});
			for (int j = 0; j < mid; j ++, wk = wk * w1) {
				auto x = a[i + j], y = wk * a[i + j + mid];
				a[i + j] = x + y, a[i + j + mid] = x - y;
			}
		}
	}
}

int qmi(int a, LL k, int p) {
	int res = 1;
	while (k) {
		if(k & 1) res = (LL)res * a % mod;
		k >>= 1;
		a = (LL)a * a % p;
	}
	return res;
}

void init() {
	while((1 << bit) < 2 * n + 1) bit ++ ;
	tot = 1 << bit;
	for (int i = 0; i < tot; i ++ ) rev[i] = ((rev[i >> 1] >> 1)) | ((i & 1) << (bit - 1));
}

void func1(vector<int> &t) {
	for (int i = 0; i < t.size(); i ++ ) {
		for (int j = i + 1; j < t.size(); j ++ ) {
			res = res * (t[j] - t[i]) % mod;
		}
	}
}

void func2(vector<int> &t) {
	for (int i = 0; i < tot; i ++ ) a[i].x = a[i].y = 0;
	for (auto & it : t) {
		a[it].x ++ ;
		a[n - it].y ++ ;
	}

	FFT(a, 1);
	for (int i = 0; i < tot; i ++ ) a[i] = a[i] * a[i];
	FFT(a, -1);
	for (int i = n + 1; i <= n * 2; i ++ ) {
		if(a[i].y < tot) continue;
		LL num = (a[i].y / (tot * 2) + 0.5);
		res = res * qmi(i - n, num, mod) % mod;
	}
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0);

	cin >> n;
	for (int i = 1; i <= n; i ++ ) {
		cin >> w[i];
		w[i] = w[i] % mod;
		arr[i] = w[i];
	}

	auto now = clock();
	sort(arr + 1, arr + n + 1);
	cnt = unique(arr + 1, arr + n + 1) - arr - 1;
	for (int i = 1; i <= n; i ++ ) {
		int t = lower_bound(arr + 1, arr + cnt + 1, w[i]) - arr;
		g[t].push_back(i);
	}

	init();
	for (int i = 1; i <= n; i ++ ) {
		if(g[i].size() <= 2683) func1(g[i]);
		else func2(g[i]);
	}

	cout << res << '\n';
	return 0;
}


posted @ 2022-04-04 17:33  晴屿  阅读(397)  评论(0编辑  收藏  举报