• CF838CFuture Failure【dp,子集卷积】


    正题

    题目链接:https://www.luogu.com.cn/problem/CF838C


    题目大意

    一个字符串\(s\),两个人轮流操作,每次每个人可以选择删掉一个字符或者重排列这个字符串,但是不能出现之前出现过的字符串,不能操作者输。

    求有多少个长度为\(n\)且字符集大小为\(k\)的字符串使得先手必胜。

    \(1\leq n\leq 250000,1\leq k\leq 26\)


    解题思路

    显然如果删掉一个字符能使得先手必败,那么先手必胜。如果不能,那么肯定会一直重排列这个字符串。

    那么如果一个字符串的排列数是偶数,那么先手可以切换先后手,所以先手必胜。否则先手需要考虑能否删除一个字符使得先手必败。

    设第\(i\)个字符的数量是\(a_i\),那么一个字符串可重排列的方案就是\(\frac{n!}{\prod_{i=1}^ka_i!}\),并且如果删除一个字符\(i\),那么排列方式将会乘上\(\frac{a_i}{n}\)

    那么如果\(n\)是奇数,肯定存在一个\(a_i\)是奇数,也就是说删除一个\(i\)后排列方式的奇偶性不变。所以如果一个\(n\)是奇数且字符串的排列数是奇数,那么肯定可以删除一个字符使得排列数仍然是偶数。
    那么如果\(n\)是奇数且先手,那么肯定不会被逼到一个走动后先手必胜的位置,所以\(n\)是奇数先手必胜。

    然后如果\(n\)是偶数,那么如果排列方式是偶数那么先手必胜否则先手必败。

    然后考虑怎么计数\(n\)是偶数的情况。考虑到一个\(n!\)包含的\(2\)质因数的个数为\(\sum_{i=0}\lfloor\frac{n}{2^i}\rfloor\),那么如果一个字符串的排列方式是偶数那么肯定有

    \[\sum_{i=0}\left\lfloor\frac{n}{2^i}\right\rfloor=\sum_{p=1}^k\sum_{i=0}\left\lfloor\frac{a_p}{2^i}\right\rfloor \]

    然后又因为假设我们考虑一直分解\(x\)出来,显然\(\prod_{i=1}^ka_i!\)\(x\)肯定不会比\(n!\)\(x\)多,同理分解\(2^k\)出来也是一样的,所以它们每一个\(i\)求出来的答案都是恰好相等的,即

    \[\forall i\in N,\left\lfloor\frac{n}{2^i}\right\rfloor=\sum_{p=1}^k\left\lfloor\frac{a_p}{2^i}\right\rfloor \]

    那么\(a_i\)求和的时候二进制就不能有进位了,也就是说对于\(n\)中的每个\(1\)\(a_i\)都恰好有一个是\(1\)\(n\)中的每一个\(0\)\(a_i\)这一位都是\(0\)

    也就是把\(n\)的二进制分成若干份\(a_i\),每一份的贡献是\(\frac{1}{a_i!}\),要求贡献的乘积和。这个分出一个部分来的转移其实就是子集卷积,所以我们跑\(k\)次子集卷积即可。

    这样跑有点慢,所以我们还需要快速幂优化。

    时间复杂度:\(O(k\log kn\log ^2n)\)


    code

    #pragma GCC optimize(2)
    %:pragma GCC optimize(3)
    %:pragma GCC optimize("Ofast")
    %:pragma GCC optimize("inline")
    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<algorithm>
    #include<vector>
    #define mp(x,y) make_pair(x,y)
    #define ll long long
    using namespace std;
    const ll N=1e6+10;
    struct node{
    	ll to,next;
    }a[N];
    ll n,m,tot,ls[N],dep[N],len[N],son[N];
    ll d[N],s[N],g[N],*f[N],*now,ans[N];
    vector<pair<ll,ll> >q[N];
    ll read() {
    	ll x=0,f=1;char c=getchar();
    	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
    	while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
    	return x*f;
    }
    void print(ll x){
    	if(x>9)print(x/10);putchar(x%10+48);return;
    }
    void addl(ll x,ll y){
    	a[++tot].to=y;
    	a[tot].next=ls[x];
    	ls[x]=tot;return;
    }
    void dfs(ll x){
    	for(ll i=ls[x];i;i=a[i].next){
    		ll y=a[i].to;dfs(y);
    		if(len[y]>=len[son[x]])son[x]=y;
    	}
    	if(son[x])len[x]=len[son[x]]+1;
    	return;
    }
    void solve(ll x){
    	if(son[x]){
    		f[son[x]]=f[x]+1;
    		solve(son[x]);
    		d[x]=d[son[x]];
    		s[x]=s[son[x]]-d[x];
    		f[x][0]-=s[x];
    	}
    	d[x]++;s[x]++;
    	for(ll i=ls[x];i;i=a[i].next){
    		ll y=a[i].to;
    		if(y==son[x])continue;
    		f[y]=now;now+=len[y]+1;solve(y);
    		int r=f[y][len[y]]+s[y]+d[y]*len[y];
    		s[x]+=r;f[x][0]-=r;
    		for(ll j=0;j<=len[y];j++)
    			f[x][j+1]+=f[y][j]+s[y]+d[y]*j-r;
    	}
    	for(ll i=0;i<q[x].size();i++){
    		ll p=min(q[x][i].first,len[x]);
    		ans[q[x][i].second]=f[x][p]+s[x]+d[x]*p;
    	}
    	return;
    }
    signed main()
    {
    	n=read();m=read();
    	for(ll i=2,x;i<=n;i++)
    		x=read(),addl(x,i);
    	for(ll i=1;i<=m;i++){
    		ll x=read(),d=read();
    		q[x].push_back(mp(d,i));
    	}
    	dfs(1);f[1]=g;now=g;now+=len[1]+1;
    	solve(1);
    	for(ll i=1;i<=m;i++)
    		print(ans[i]),putchar('\n');
    	return 0;
    }
    
  • 相关阅读:
    Data Wrangling文摘:Non-tidy-data
    Data Wrangling文摘:Tideness
    Python文摘:Mixin 2
    Python文摘:Mixin
    Python文摘:Python with Context Managers
    Python学习笔记11
    SQL学习笔记9
    SQL学习笔记8
    SQL学习笔记7
    Python学习笔记10:内建结构
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/16084426.html
Copyright © 2020-2023  润新知