CSP-S模拟19
即将参加CSP-S的我中午被告知“我终于要有自己的电脑了”!?不禁好奇它是什么样子的……惊喜之余冰冷的声音传入脑海,似水波荡漾,又似空谷回响:“你配吗?”……
A. 木棍
我并不会贪心,但数据范围告诉我:你可以放心的去生成排列,不会T。至于正解:
t1是arc126a,之前搬过这场的CD。发现肯定用偶数个3,所以可以把234变成246,贪心地选46,226,442,4222,22222就可以了,很容易用调整法证明。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 123;
ll T, n1, n2, n3, a, ans;
int b[7];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
T = read();
while(T--)
{
scanf("%lld%lld%lld", &n1, &n2, &n3);
ll d1 = n1, d2 = n2, d3 = n3; ans = 0;
for(int i=1; i<=5; i++) b[i] = i;
do
{
ll res = 0; n1 = d1, n2 = d2, n3 = d3;
for(int i=1; i<=5; i++)
{
if(b[i] == 1)
{
a = min(n3/2, n1);
n1 -= a; n3 -= a*2; res += a;
}
else if(b[i] == 2)
{
a = min(n2/2, n3);
n2 -= a*2; n3 -= a; res += a;
}
else if(b[i] == 3)
{
a = min(n2/2, n1/2);
n1 -= a*2; n2 -= a*2; res += a;
}
else if(b[i] == 4)
{
a = min(n3, n1/3);
n1 -= a*3; n3 -= a; res += a;
}
else
{
a = n1/5; n1 -= a*5;
res += a;
}
}
ans = max(ans, res);
}while(next_permutation(b+1,b+6));
printf("%lld\n", ans);
}
return 0;
}
B. 环
鹤题解鹤了一下午+一晚上……
先将a[i]从小到大排序,保证前面能选后面一定能选。设f[i][j][k]表示考虑左侧的前i个点和右侧的前a[i]个点,右侧的点中有j个单点和k条链(概念是这个链必须以右侧的点开始到右侧的点结束,好像这个已经和后面的有向有关系了,可能题解原来不是这个意思但是三维dp我并没有写),为了不重复统计答案,固定i是每个环出现在左侧的最大的点。
发现记录两维j,k的原因是单点和单点相连的方案数是1,单点和链相连的方案数是2,链和链相连的方案数是4。但是依然可以合并,把单点和链都看成有向的(就是从一个点向另一个点连有向边,最后合法的环需要方向一致,首尾相接),这样每个环会被顺时针和逆时针统计一次,于是最后答案乘上inv2。
考虑由f[i-1]到f[i]先不考虑左侧的新点i加入拼接的贡献,现在只加入了右侧的a[i]-a[i-1]个新点。由于统计答案的时候会固定左侧的i是连接点,在把i连进去之前左侧到i只是一个范围,所以f[i][j]包含f[i-1]...的所有情况,可以直接继承,然后挨个把右侧的单点加入组合。
有一种组合数的写法,它可以A但是不判断边界的话就会莫名其妙TLE 30:(用Chen_jr的TLE 30改的)
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const int mod = 998244353;
const int inv2 = 499122177;
int n, a[maxn], f[maxn][maxn];
void add(int &x, int y) {x += y; x = x > mod ? x - mod : x;}
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int fac[maxn], inv[maxn];
ll qpow(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int C(int n, int m) {return 1ll * fac[n] * inv[m] % mod * inv[n-m] % mod;}
int main()
{
n = read();
for(int i=1; i<=n; i++) a[i] = read();
fac[0] = inv[0] = 1;
for(int i=1; i<=n; i++) fac[i] = 1ll * fac[i-1] * i % mod;
inv[n] = qpow(fac[n], mod-2);
for(int i=n-1; i>=1; i--) inv[i] = 1ll * inv[i+1] * (i+1) % mod;
sort(a+1, a+1+n);
int ans = 0; f[0][0] = 1;
for(int i=1; i<=n; i++)
{
for(int j=0; j<=a[i-1]; j++)
{
for(int k=j; k<=a[i]; k++)
{
if(k - j > a[i] - a[i-1]) break;
add(f[i][k], 1ll*f[i-1][j]*C(a[i]-a[i-1], k-j)%mod);
}
}
add(ans, (f[i][1]-a[i]+mod)%mod);
for(int j=0; j<=a[i]; j++) add(f[i][j], 1ll*f[i][j+1]*(j+1)%mod*j%mod);
}
ans = 1ll * ans * inv2 % mod;
printf("%d\n", ans);
return 0;
}
if(k-j > a[i]-a[i-1]) break;
也可以用背包dp的思想拆掉组合数。
接着统计答案,答案就是一条链除去单点的方案个数,这时左侧点i不参与组成链,加上了i的操作就是用i把所有单链的两端接上。
加入i把两条链连成一条是在统计i对i+1的贡献,有两种方案把两条有向链连起来,连完的结果依然是链并且由于连之前保证概念(右侧开始右侧结束,一开始加的都是右侧的单点肯定合法,用左边的一个点把右边的两个连起来也合法,每一个加入合法链条集合的左侧点一定是因为对应了两个右侧点才能加入),连之后没有破坏。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const int mod = 998244353;
const int inv2 = 499122177;
int n, a[maxn], f[maxn];
void add(int &x, int y) {x += y; x = x > mod ? x - mod : x;}
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read();
for(int i=1; i<=n; i++) a[i] = read();
sort(a+1, a+1+n);
int ans = 0; f[0] = 1;
for(int i=1; i<=n; i++)
{
for(int j=a[i-1]+1; j<=a[i]; j++)
{
for(int k=j; k>=1; k--)
{
add(f[k], f[k-1]);
}
}
add(ans, (f[1]-a[i]+mod)%mod);
for(int j=0; j<=a[i]; j++) add(f[j], 1ll*f[j+1]*(j+1)%mod*j%mod);
}
ans = 1ll * ans * inv2 % mod;
printf("%d\n", ans);
return 0;
}
C. 传令
我连二分都没想到,居然以为直接贪心可以做,打算把树上的边断掉,然后新形成的森林中最深max的树可以去更新答案min。。于是枚举断边集合,每个时刻断一次,然后套上个线段树分治发现并查集不能得到深度***觉得我不用再去搞线段树分治的例题了。。
先二分答案(r可以取n,不过更缩小范围的方案是取树的直径)固定一个根,每次找到当前最深的没有覆盖的点,用他的k级祖先区覆盖他,k级祖先可以用倍增找。关于树剖+线段树,单点修改子树查询?我没鹤到代码所以很不理解。
code TLE 78
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 3;
const int inf = 0x7ffffff;
int n, K, cnt, siz[maxn], mx[maxn], dep[maxn], dfn[maxn];
int ms[maxn], f[maxn][12], root, sum;
bool vis[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct node
{
int next, to;
}a[maxn<<1];
int head[maxn], len;
void add(int x, int y)
{
a[++len].to = y; a[len].next = head[x];
head[x] = len;
}
bool cmp(int a, int b)
{
return dep[a] > dep[b];
}
void findroot(int x, int fa)
{
siz[x] = 1; mx[x] = 0;
for(int i=head[x]; i; i=a[i].next)
{
int v = a[i].to;
if(v == fa) continue;
findroot(v, x);
siz[x] += siz[v];
mx[x] = max(mx[x], siz[v]);
}
mx[x] = max(mx[x], sum-siz[x]);
if(mx[x] < mx[root]) root = x;
}
void dfs(int x, int ft)
{
dep[x] = dep[ft] + 1;
f[x][0] = ft;
for(int i=head[x]; i; i=a[i].next)
{
int v = a[i].to;
if(v == ft) continue;
dfs(v, x);
}
}
int Fairy(int x, int k)
{
int t = dep[x] - k;
for(int i=10; i>=0; i--)
{
if(dep[f[x][i]] > t) x = f[x][i];
}
return f[x][0];
}
void col(int u, int ft, int dis, int x)
{
vis[u] = 1; ms[u] = min(ms[u], dis);
if(dis == x) return;
for(int i=head[u]; i; i=a[i].next)
{
int v = a[i].to;
if(v == ft || ms[v] < dis + 1) continue;
ms[v] = dis + 1;
col(v, u, dis+1, x);
}
}
bool check(int x)
{
cnt = 0;
for(int i=1; i<=n; i++) vis[i] = 0, ms[i] = inf;
for(int i=1; i<=n; i++)
{
int u = dfn[i];
if(vis[u]) continue;
cnt++;
if(dep[u] <= x + 1) u = root;
else u = Fairy(u, x);
col(u, u, 0, x);
}
return cnt <= K;
}
int solve()
{
int l = 1, r = dep[dfn[1]];
while(l < r)
{
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
int main()
{
n = read(); K = read();
for(int i=1; i<n; i++)
{
int x = read(), y = read();
add(x, y); add(y, x);
}
mx[0] = sum = n; findroot(1, 0);
dfs(root, 0);
for(int j=1; j<=10; j++)
{
for(int i=1; i<=n; i++) f[i][j] = f[f[i][j-1]][j-1];
}
for(int i=1; i<=n; i++) dfn[i] = i;
sort(dfn+1, dfn+1+n, cmp);
printf("%d\n", solve());
return 0;
}
正解是树形dp:
对于一棵子树,有可能被完全覆盖了,也有可能遇到一个叶子深度小于k,这个时候暂时不去覆盖它。
完全覆盖的情况我们记录树根往外延伸的距离。有叶子没覆盖的情况就记录这个叶子的深度,这种情况不用记录上个情况的距离,我们最后是用子树外的点去覆盖它,这个点从树根延伸出去的距离肯定大于当前的距离。直接合并子树信息即可。
如果一个节点到自己子树内的最远未覆盖子节点的距离刚好超过mid,那这个点就需要被直接通知,问题是怎么找到i到子树内被直接通知的点的最大距离,设为g[i],再设一个f[i]是i到子树内所有被直接通知的点的最小距离。从子树向上多一条边这两个值都是直接+1。
用g[i]+f[i]判断g[i]是否可以不存在,在单独用g[i]判断是否需要新添加。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 3;
const int inf = 0x7ffffff;
int l, r, up, cnt, n, K, g[maxn], f[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct node
{
int next, to;
}a[maxn<<1];
int head[maxn], len;
void add(int x, int y)
{
a[++len].to = y; a[len].next = head[x];
head[x] = len;
}
int dfs1(int x, int fa)
{
int sum1 = 0, sum2 = 0;
for(int i=head[x]; i; i=a[i].next)
{
int y = a[i].to;
if(y == fa) continue;
sum2 = max(sum2, dfs1(y, x)+1);
if(sum2 > sum1) swap(sum2, sum1);
}
up = max(up, sum1 + sum2);
return sum1;
}
void dfs2(int x, int fa, int d)
{
f[x] = inf; g[x] = 0;
for(int i=head[x]; i; i=a[i].next)
{
int y = a[i].to;
if(y == fa) continue;
dfs2(y, x, d);
f[x] = min(f[x], f[y]+1);
g[x] = max(g[x], g[y]+1);
}
if(f[x]+g[x] <= d) g[x] = -inf;
else if(g[x] == d)
{
g[x] = -inf, f[x] = 0; cnt++;
}
}
bool check(int d)
{
cnt = 0;
dfs2(1, 0, d);
if(g[1] >= 0) cnt++;
return cnt <= K;
}
int main()
{
n = read(); K = read();
for(int i=1; i<n; i++)
{
int x = read(), y = read();
add(x, y); add(y, x);
}
dfs1(1, 0);
r = up;
while(l < r)
{
int mid = (l + r) >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}
D. 序列
鹤已耗尽我所有的力量,所以不写了。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 25003;
const ll mod = 1e9 + 7;
int n, m, k, a[maxn], ans, f[maxn][405], g[maxn][405], l, r;
bool vis[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
ll qpow(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
bool checkcolor()
{
for(int i=1,j=k; j<=m; j++,i++)
{
for(int p=1; p<=k; p++) vis[p] = false;
for(int p=i; p<=j; p++) if(vis[a[p]]) goto nxt; else vis[a[p]] = true;
return true; nxt:;
}
return false;
}
void solveunique()
{
f[0][0] = 1;
for(int i=1; i<=n; i++)
{
for(int j=1; j<k; j++)
{
f[i][j] = (f[i-1][j]+1ll*(f[i-1][j-1]-f[i-1][j]+mod)%mod*(k-j+1))%mod;
g[i][j] = (g[i-1][j]+1ll*(g[i-1][j-1]-g[i-1][j]+mod)%mod*(k-j+1))%mod;
if(j >= m) g[i][j] = (g[i][j]+f[i][j])%mod;
}
for(int j=k-1; j>=0; j--) f[i][j] = (f[i][j]+f[i][j+1])%mod;
for(int j=k-1; j>=0; j--) g[i][j] = (g[i][j]+g[i][j+1])%mod;
}
int nans = g[n][0];
for(int i=k; i>k-m; i--) nans = 1ll*nans*qpow(i, mod-2)%mod;
ans = (ans-nans+mod)%mod;
}
void calc(int f[][405])
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<k; j++) f[i][j] = (f[i-1][j]+1ll*(f[i-1][j-1]-f[i-1][j]+mod)%mod*(k-j+1)%mod)%mod;
for(int j=k-1; j>=0; j--) f[i][j] = (f[i][j]+f[i][j+1])%mod;
}
}
void solvemulti()
{
for(int i=0; i<=l; i++) f[0][i] = 1;
for(int i=0; i<=r; i++) g[0][i] = 1;
calc(f); calc(g);
int nans = 0;
for(int i=0; i<=n-m; i++) nans = (nans+1ll*f[i][0]*g[n-m-i][0]%mod)%mod;
ans = (ans-nans+mod)%mod;
}
int main()
{
n = read(); k = read(); m = read();
for(int i=1; i<=m; i++) a[i] = read();
ans = 1ll*qpow(k, n-m)*(n-m+1)%mod;
if(checkcolor()) goto P;
l = 0; r = m + 1;
for(int i=1; i<=k; i++) vis[i] = false;
for(int i=1; i<=m; i++)
{
if(vis[a[i]]) {l = i - 1; break;}
else vis[a[i]] = true;
}
for(int i=1; i<=k; i++) vis[i] = false;
for(int i=m; i>=1; i--)
{
if(vis[a[i]]) {r = m - i; break;}
else vis[a[i]] = true;
}
if(l == 0 && r == m + 1) solveunique();
else solvemulti();
P:;
printf("%d\n", ans);
return 0;
}