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\) 个长方形顺次拼接组成的多边形的周长为:
答案涉及到了相邻两个长方形的高度差。
考虑状压 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\),则有状态转移方程:
\(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\) 的攻击。
转移有以下两个:
-
使用 \(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\)。
-
使用 \(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[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;
}