chy2003 大爺出的題。
本來這套題一眼看上去對我這種蒟蒻挺友好的,但是,反正每次只要是我自我感覺蠻良好的時候,最後成績都挺慘的。
期望得分 240 ,實際得分 175 。
T1 night
題意
有一些 位二進制數 和一個 ,假如 在二進制下是 1 的位 都是 1,那麼 可以減去 。
有多組詢問,每次問有多少個 能夠通過一些減 的操作得到 。
思路
首先確定這題是一個狀壓 DP。 表示 能由多少個數操作得到。
那麼現在問題在於去重,因爲有的 可能可以由 通過多次減操作得到,但是 只能對 有 1 的貢獻。
先講我的 的做法,非常 naive。記 ,在 的基礎上多記一位 表示這個 是由 減去 得到的。然後卡常能卡到 2 秒 真是可惜 。
然後是 做法。只需要記 ,然後改變循環的順序。先枚舉 中的 ,也就是強行規定先做 小的操作。那麼操作的順序問題顯然就迎刃而解了。
代碼
#include<bits/stdc++.h>
using namespace std;
const int M = 21, S = 1<<20;
int T, n, m, c, b, q;
int f[S];
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
int main()
{
for (read(T); T--; ){
read(n); read(m);
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++ i){
int x; read(x);
f[x]++;
}
b = 1;
for (read(c); c--; ){
int x; read(x);
b |= 1<<x;
}
for (; b < (1<<m); b <<= 1)
for (int i = (1<<m)-1; i >= 1; -- i)
if ((i&b) == b)
f[i^b] += f[i];
for (read(q); q--; ){
int x; read(x);
printf("%d\n", f[x]);
}
}
return 0;
}
T2 dawn
題意
有一棵樹,求 條路徑的排列,使這 條路徑的交爲 到 的簡單路徑。
注意:假如選兩條路徑,那麼 和 是兩種不同的方案,但是 和 是兩種相同的方案。
思路
相當於在兩棵子樹各選 個點,兩兩對應形成路徑,答案爲 , 爲 的子樹大小。
然後發現假如 個點都在 的同一個兒子的子樹裏,那麼路徑的交會比 要長,那就把這些答案減掉。重複減的容斥一下。
最後只需要對每個點記錄子樹大小的 次方,所有兒子子樹大小的 次方和,然後還要記錄子樹外的那棵子樹的信息,用來特判 某一個是另一個的祖先。
注意
檢查取模!!!
檢查取模!!!
檢查取模!!!
不檢查 CSP 爆零活該。
代碼
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = N<<1, E = 20, K = 100 + 5, mod = 998244353;
int T, n, q;
int h[N], ecnt, nxt[M], v[M];
int f[N][E], dep[N];
int siz[N][K], sum[N][K], tsiz[N][K];
template<class T>inline void add(T &x, T y){x += y; if (x >= mod) x -= mod;}
template<class T>inline void read(T &x){
x = 0; bool fl = 0; char c = getchar();
while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
if (fl) x = -x;
}
void _add(int x, int y){
nxt[++ecnt] = h[x]; v[ecnt] = y;
h[x] = ecnt;
}
void dfs(int u, int fa){
dep[u] = dep[fa]+1;
f[u][0] = fa;
for (int i = 1; i < E; ++ i)
f[u][i] = f[f[u][i-1]][i-1];
siz[u][1] = 1;
for (int i = 1; i < K; ++ i)
sum[u][i] = 0;
for (int i = h[u]; i; i = nxt[i])
if (v[i] != fa){
dfs(v[i], u);
add(siz[u][1], siz[v[i]][1]); // 沒有取模!!!
for (int j = 1; j < K; ++ j)
add(sum[u][j], siz[v[i]][j]); // 沒有取模!!!
}
for (int i = 2; i < K; ++ i)
siz[u][i] = 1LL * siz[u][i-1] * siz[u][1] % mod;
if (u == 1) tsiz[u][1] = 0;
else tsiz[u][1] = n-siz[u][1];
for (int i = 2; i < K; ++ i)
tsiz[u][i] = 1LL * tsiz[u][i-1] * tsiz[u][1] % mod;
}
int lca(int x, int y){
if (dep[x] < dep[y]) swap(x, y);
for (int i = E-1; i >= 0; -- i)
if (dep[f[x][i]] >= dep[y])
x = f[x][i];
if (x == y) return x;
for (int i = E-1; i >= 0; -- i)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int lca_son(int x, int y){
if (dep[x] < dep[y]) swap(x, y);
for (int i = E-1; i >= 0; -- i)
if (dep[f[x][i]] > dep[y])
x = f[x][i];
return x;
}
int solve1(int x, int y, int k){
LL ans = 0;
add(ans, 1LL * siz[x][k] * siz[y][k] % mod);
add(ans, mod - 1LL * sum[x][k] * siz[y][k] % mod);
add(ans, mod - 1LL * sum[y][k] * siz[x][k] % mod);
add(ans, 1LL * sum[x][k] * sum[y][k] % mod);
return ans;
}
int solve2(int x, int y, int k){
if (dep[x] > dep[y]) swap(x, y);
LL ans = 0;
int z = lca_son(x, y);
int tmp = ((sum[x][k]-siz[z][k]+tsiz[x][k])%mod+mod)%mod;
add(ans, 1LL * siz[y][k] * tsiz[z][k] % mod);
add(ans, mod - 1LL * tmp * siz[y][k] % mod);
add(ans, mod - 1LL * sum[y][k] * tsiz[z][k] % mod);
add(ans, 1LL * tmp * sum[y][k] % mod);
return ans;
}
int main()
{
for (read(T); T--; ){
read(n); read(q);
ecnt = 1; memset(h, 0, sizeof(h));
for (int i = 1; i < n; ++ i){
int x, y;
read(x); read(y);
_add(x, y); _add(y, x);
}
dfs(1, 0);
for (int i = 1; i <= q; ++ i){
int k, x, y, z;
read(k); read(x); read(y);
z = lca(x, y);
if (z == x || z == y) printf("%d\n", solve2(x, y, k));
else printf("%d\n", solve1(x, y, k));
}
}
return 0;
}
T3 light
題意
有 個格子,標號 到 ,可以花費 的代價得到 號格子。有一些格子,在你花錢得到他的時候同時也會得到 的所有格子。
現在給定 和這些特殊格子,求最小的代價得到所有 個格子。
思路
先寫出 DP 方程: ,其中 號格子是特殊格子,並且滿足
。
貪心一波,對於某個 ,顯然會選 的最小的 。然後用單調隊列維護一個區間最小的 就完事了。
然後講一下我的錯誤思路:對於每一個位置 ,假如他是特殊位置,就拿他和
來更新 。顯然這個思路漏了 的 ,也就是沒有考慮兩個選取的特殊位置相互覆蓋的情況。
代碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long LL;
const int N = 5e6 + 10;
const LL inf = 1e18 + 7;
int T, n, k;
char s[N];
namespace Solver3
{
LL f[N];
int que[N], h, t;
int que1[N], h1, t1;
void main(){
f[0] = 0;
que[h = t = 1] = 0;
h1 = 1; t1 = 0;
for (int i = 1; i <= n; ++ i){
f[i] = f[i-1] + i;
if (s[i] == '1') que1[++t1] = i;
while (h1 <= t1 && que1[h1]+k < i) h1++;
if (h1 <= t1){
while (h <= t && que[h] < que1[h1]-k-1) h++;
if (h <= t) f[i] = min(f[i], f[que[h]] + que1[h1]);
}
while (h <= t && f[que[t]] >= f[i]) t--;
que[++t] = i;
}
printf("%lld\n", f[n]);
}
}
signed main()
{
for (scanf("%lld", &T); T--; ){
scanf("%lld%lld%s", &n, &k, s+1);
Solver3::main();
}
return 0;
}