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] 表示从 2n 最多能开方 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
然后一起求前缀异或和
然后我们开始 i0n 这样操作:
往字典树插入: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 个长方形的高度为 ai,则这 n 个长方形顺次拼接组成的多边形的周长为:

2n+a1+an+1i<n|aiai+1|

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

考虑状压 dp。

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

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

当集合 S 只有 x 这一个元素的时候有 FS,x=axGS,x=1

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

FS,x=max{FT,y+|axay|}

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

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

单组数据的时间复杂度为 O(2nn2)

#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(nm2)的DP

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

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

不将第i+1个点加入连通块,i+1子树内的所有点也一定不会被加入连通块, 又由于i+1子树在DFS序上是一段连续的区间,可以得到转移 f(i+sizei,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

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

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

我们有了一个 DP 模型:

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

转移有以下两个:

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

    f(i,j,k)f(i1,jh,kr)+bloodi9×h3×r

    上式必须满足 h+r+bloodi9×h3×rmid(即第 i 个妖怪不受到超过 mid 次伤害)且 hj,rk

  2. 使用 h 次伤害为 9 的攻击和 r 次伤害为 3 的攻击恰好消灭第 i 个妖怪。
    「恰好消灭」的定义:9×h+3×rbloodi, 且 (h1,r)(h>0)(h,r1)(r>0) 都无法将其消灭。

    f(i,j,k)f(i1,jh,kr)

    上式必须满足 h+rmidhj,rk。当枚举一个 hr 就能确定。

判定:如果 min{f(m,j,k)}mid,则 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, 否则就直接n2暴力

一次FFT的时间为T=2MlogMM=2MAXN=72105

设当一组数据的大小 大于 x时用fft跑,否则 n2暴力跑

因此 x2(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 @   晴屿  阅读(408)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示