Codeforces Round #808 (Div. 1)
\(\texttt{Rating Change:}\color{orange}{2149}\color{black}\to \color{orange}{2252}\)
\(\Delta={\color{green}{\texttt{103}}}\qquad \texttt{rank:157}\)
A
迷惑题,题意太长不讲了。
反正就是容易想到前面跳过几个打不过的测试,与最终能够进行到那里肯定是单调的。并且我们希望跳过的越少越好。那么直接二分答案,然后能打就打,能跳就跳,实在不行再牺牲一下智商。
My Code
#define int long long
using namespace std;
const int MAXN=1e5+10;
int a[MAXN],n,q;
int ans[MAXN];
bool check(int mid){
int Q=q,skp=n-mid;
rep(i,1,n) ans[i]=1;
rep(i,1,n){
if(a[i]>Q){
if(skp) ans[i]=0,skp--;
else if(Q) Q--;
else return 0;
}
}return 1;
}
void solve(){
cin>>n>>q;
rep(i,1,n) cin>>a[i];
int l=0,r=n,ok=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) l=mid+1,ok=mid;
else r=mid-1;
}bool trash=check(ok);
rep(i,1,n) cout<<ans[i];
cout<<'\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;)solve();
return 0;
}
B
复杂度分析题。
你注意到这东西没有什么高明的加速方法,于是想到暴力的复杂度有可能是对的。然后看到题目保证所有数据中 \(\sum a_i\le 5\times 10^5\),想到可能和值域有关。考虑到差分的时候,如果最左边出现了 \(0\),那这些 \(0\) 是不会参与排序和差分的(除了最后一个),这就是优化的关键。然后你考虑每一个数被差分后最多 \(O(\log v)\) 次就变成 \(0\) 了,否则这个序列最多也就 \(O(\log n)\) 的长度。所以暴力复杂度加上每次的排序应该是 \(O(n\log n\log v)\) 的。
做法就是用一个指针维护最右边的 \(0\) 的位置,然后只要注意时刻判 \(0\) 就行了。
My Code
using namespace std;
const int MAXN=1e5+10;
int a[MAXN];
void solve(){
int n;cin>>n;
rep(i,1,n) cin>>a[i];
int r=n,l=0;
rep(i,1,n-1){
while(a[l+1]==0) l++;
if(l>r){cout<<0<<'\n';return;}
rep(j,l,r-1) if(j) a[j]=a[j+1]-a[j];
if(a[l]) l--;
r--;if(l>r){cout<<0<<'\n';return;}
sort(a+l+1,a+1+r);
}cout<<a[1]<<'\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;for(cin>>T;T--;)solve();
return 0;
}
C
图论思维题。
考虑一波 dfs 的性质,它一定是将某一棵子树完全遍历才会跳到另一棵去。于是我们想到,先建一棵 MST,然后枚举根 \(u\),如果以 \(u\) 为根的时候,不在 MST 里的边对这棵 MST 造成了横叉边,那么 dfs 的时候就会把这条边加进去,就不合法了。所以,某一个根合法的条件是加入不在 MST 里的边后只存在返祖边。
然后考虑怎么统计。此时,不妨把 MST 当成一棵无根树,这样的话,对于任意一条不在 MST 里的边 \((x,y)\),它会成为不在 \(x,y\) 向外的子树内的点为根的树的横叉边,这个可以自己手模一下看看。那也就是说,只有当某一个点 \(\forall (x,y)\),只处于他们子树中时是合法的。
好这样就考虑是否可以用 DS 来维护这些信息。不妨,我们每次对于 \((x,y)\),先让全局加一,然后在子树内减一,这样最后每个节点的权值如果是 \(0\),那么就是合法的。
具体做的时候,可以先把 \(1\) 当成根,然后如果就是横叉边,就直接差分数组上根加一,\(x,y\) 减一;如果不是,那么就从深度大的向上倍增到深度小的的儿子,假设是 \(dep_x<dep_y\),并且倍增到 \(z\),那么就在 \(z\) 上加一,然后 \(y\) 上减一。最后下放差分数组就可以了。
My Code
using namespace std;
const int MAXN=2e5+10;
int u[MAXN],v[MAXN],f[MAXN];
int find(int x){while(x^f[x])x=f[x]=f[f[x]];return x;}
vector<int> e[MAXN];
int fa[MAXN][20],dep[MAXN],dfn[MAXN],siz[MAXN],tot;
void dfs(int x,int fat){
fa[x][0]=fat;dfn[x]=++tot;siz[x]=1;
rep(i,1,19) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int s:e[x]){
if(s==fat) continue;
dep[s]=dep[x]+1;
dfs(s,x);
siz[x]+=siz[s];
}
}
int cf[MAXN];
void dfs1(int x,int fat){
for(int s:e[x]){
if(s==fat) continue;
cf[s]+=cf[x];
dfs1(s,x);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,m;cin>>n>>m;
iota(f+1,f+1+n,1);
rep(i,1,m) cin>>u[i]>>v[i];
vector<pii> op;
rep(i,1,m){
if(find(u[i])==find(v[i])){
op.pb(mkp(u[i],v[i]));
continue;
}f[find(u[i])]=find(v[i]);
e[u[i]].pb(v[i]);
e[v[i]].pb(u[i]);
}dfs(1,0);
for(auto ed:op){
int x=ed.fi,y=ed.se;
if(dep[x]>dep[y]) swap(x,y);
if(dfn[x]+siz[x]-1<dfn[y]||dfn[y]<dfn[x]){
cf[1]++;cf[x]--;cf[y]--;
}else{
cf[y]--;
per(i,19,0)
if(dep[fa[y][i]]>dep[x])
y=fa[y][i];
cf[y]++;
}
}dfs1(1,0);
rep(i,1,n)
if(cf[i]==0) cout<<1;
else cout<<0;
return 0;
}
D
牛逼数数题。
给你一棵树,每次操作是选取树上的一棵虚树,求恰好 \(k\) 次后只剩下根的方案数是多少。
考虑是方案计数题,果断考虑大炮。令 \(dp_{x,i}\) 表示恰好 \(i\) 次删去 \(x\) 的子树的方案数。需要注意的是,题目要求每次选取的虚树必须不和原来相同,也就是不能同时选整棵树来浪费一次操作。这使得我们的计数变得比较麻烦,不妨我们先忽略这个条件。因为这一条限制只与根有关。
那么的话,考虑 \(dp_{x,i}\) 的转移。首先我们发现对于 \(x\) 而言,如果他有两棵及以上的子树需要保留,那么 \(x\) 必须要保留。否则可以选择保留 \(x\) 或者 删去 \(x\)。那我们假设 \(j\) 时刻(\(j\le i\))删去了 \(x\),然后枚举最后删去的儿子是 \(u\),那么在此之前 \(x\) 都不可能被删去,那我们在 \(j-1\) 步把其它子树删掉,然后删掉 \(x\),然后是 \(u\) 的子树。其他子树 \(j-1\) 步删完的方案数在我们忽略了不能全保留的限制的情况下,对于子树的要求就没有那么高了,我们设 \(S_{x,i}=\sum_{j\le i}dp_{x,j}\),则有:
其中 \(s\) 是我们选定的最后留下的那个子树。然后你考虑 \(x\) 完全可以单独再删去,即没有所谓最后再删的子树,所以最后再加上一个:
那么的话转移就是:
注意这里为了实现的方便,枚举的 \(j\) 其实是删去 \(x\) 的前一时刻。
那我们令 \(sta_{x,i}=\prod_{u\in son_x}S_{u,i}\),这样我们能够快乐转移了:
注意把那个分数预处理一下就 \(O(n^2)\) 了。
然后由于根不用删,所以只需要上面最后的那个式子就可以了。
然后再考虑不能全选。上面提到过,只有在根的时候需要考虑这种情况,那么的话我们直接令最终答案是 \(A_i\) 表示恰好 \(i\) 次删去除了 \(1\) 的节点,则有:
也就是我们枚举有多少次操作不是全选的。那我们移项,得到:
然后递推求就可以了,复杂度 \(O(n^2)\)。
然后这里需要注意一下,就是有在 \(dp\) 转移的时候,有可能 \(S\) 在模 \(p\) 意义下是 \(0\),所以不能仅仅预处理逆元,最好的实现方式是把所有儿子存下来,然后维护前缀积与后缀积,然后对于一个 \(u\),取前缀的 \(S\),我们记作 \(pre\),和后缀的 \(S\),我们记作 \(suf\),那那个式子实际上就是 \(pre_{u-1}\times suf_{u+1}\)。那转移就变成了:
My Code
#define int long long
using namespace std;
const int MAXN=2010;
int n,MOD;
int ksm(int a,int p){
int ret=1;while(p){
if(p&1) ret=ret*a%MOD;
a=a*a%MOD; p>>=1;
}return ret;
}
int inv(int x){return ksm(x,MOD-2);}
int fac[MAXN],ifac[MAXN];
int pre[MAXN][MAXN],suf[MAXN][MAXN];
void init(){
fac[0]=ifac[0]=1;
rep(i,1,MAXN-10) fac[i]=fac[i-1]*i%MOD;
ifac[MAXN-10]=inv(fac[MAXN-10]);
per(i,MAXN-11,1) ifac[i]=ifac[i+1]*(i+1)%MOD;
rep(i,0,MAXN-10) rep(j,0,MAXN-10)
pre[i][j]=suf[i][j]=1%MOD;
}
int C(int a,int b){return fac[a]*ifac[b]%MOD*ifac[a-b]%MOD;}
int dp[MAXN][MAXN],S[MAXN][MAXN];
vector<int> e[MAXN];
void dfs(int x,int fa){
int son=0;
for(int s:e[x]){
if(s==fa) continue;
dfs(s,x);
}
for(int s:e[x]){
if(s==fa) continue;
son++;
rep(j,0,n) pre[son][j]=suf[son][j]=S[s][j];
}
if(!son){
rep(j,1,n) dp[x][j]=1,S[x][j]=(S[x][j-1]+dp[x][j])%MOD;
return;
}
rep(j,0,n){
rep(i,2,son) pre[i][j]=pre[i][j]*pre[i-1][j]%MOD;
per(i,son-1,1) suf[i][j]=suf[i][j]*suf[i+1][j]%MOD;
}
rep(j,1,n) dp[x][j]=suf[1][j];
if(x!=1){
int tot=0,sum=0;
for(int s:e[x]){
if(s==fa) continue;
tot++;sum=0;
rep(j,1,n){
dp[x][j]=(dp[x][j]+dp[s][j]*sum%MOD)%MOD;
sum=(sum+pre[tot-1][j]*suf[tot+1][j]%MOD)%MOD;
}
}
}
S[x][0]=dp[x][0];
rep(j,1,n) S[x][j]=(S[x][j-1]+dp[x][j])%MOD;
rep(i,1,son) rep(j,0,n) pre[i][j]=suf[i][j]=1%MOD;
}
int A[MAXN];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int x,y;cin>>n>>MOD;
init();
rep(i,1,n-1){
cin>>x>>y;
e[x].pb(y);e[y].pb(x);
}
dfs(1,0);
A[0]=dp[1][0];
rep(i,1,n-1){
A[i]=dp[1][i];
rep(j,0,i-1)
A[i]=((A[i]-C(i,j)*A[j]%MOD)%MOD+MOD)%MOD;
cout<<A[i]<<' ';
}
return 0;
}