Warning: session_start(): open(/tmp/sess_4d90b9ea87bad2948486ccf12414407c, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430
Writing /data/wiki/data/cache/d/de2edb2fcb553ea79b79c722a4e13dbc.captchaip failed

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:legal_string:lgwza:可逆背包 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:legal_string:lgwza:可逆背包

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:lgwza:可逆背包 [2021/07/16 15:12]
lgwza
2020-2021:teams:legal_string:lgwza:可逆背包 [2021/07/16 22:58] (当前版本)
lgwza
行 68: 行 68:
     }     }
     return 0;     return 0;
 +}
 +</​code>​
 +</​hidden>​
 +
 +==== 例 2 ====
 +
 +[[https://​codeforces.com/​contest/​1111/​problem/​D|CF 1111 D. Destroy the Colony]]
 +
 +=== 题意 ===
 +
 +给定一个长度为偶数的由大小写字母组成的字符串 $s$,一个“好”字符串可通过重排字母顺序使得所有相同字母的位置都在 $s$ 的同一半($[1,​n/​2] or [n/​2+1,​n]$)来生成。$q$ 个询问,给定一对 $(i,​j)$,要求字母 $s[i],s[j]$ 也在 $s$ 的同一半的“好”字符串数量。
 +
 +=== 题解 ===
 +
 +注意到询问本质上最多只有 $52^2$ 种可能,则考虑先把所有询问算出来,最后 $O(1)$ 回答询问。
 +
 +  - 令 $k$ 为字母种数,统计各字母的出现次数 $c_1,​c_2,​\cdots,​c_k$,若有 $i_1,​i_2,​\cdots,​i_p$ 使得 $c_{i_1}+c_{i_2}+\cdots+c_{i_p}=\dfrac{n}{2}$,则把它们放到第一组,剩下的放到第二组,第一组的排列数为 $\dfrac{(\dfrac{n}{2})!}{c_{i_1}!c_{i_2}!\cdots c_{i_p}!}$,第二组排列数为 $\dfrac{(\dfrac{n}{2})!}{c_{j_1}!c_{j_2}!\cdots c_{j_s}!}$,总方法数为 $W=\dfrac{(\dfrac{n}{2})!(\dfrac{n}{2})!}{c_1!c_2!\cdots c_k!}$;
 +  - 注意到 $c_i$ 求和为 $\dfrac{n}{2}$ 的组合数可用 01 背包来求,在没有 $(i,j)$ 限制下的“好”字符串数量为 $W*dp[n/​2]*2$;
 +  - 现考虑 $(i,j)$ 的限制,即字母 $s[i]$ 和 $s[j]$ 需在同一组中,等价于去掉字母 $s[i]$ 和 $s[j]$ 的 01 背包问题,即作了一次 01 背包问题后,再枚举 $i,j$ 做退背包过程,将去掉 $(i,j)$ 的 DP 值记作 $ans[i][j]$;
 +  - 最后对每个询问直接 $O(1)$ 输出答案即可。
 +
 +<hidden 查看代码>​
 +<code cpp>
 +#​include<​bits/​stdc++.h>​
 +using namespace std;
 +const int N=1e5+5,​mod=1e9+7;​
 +typedef long long ll;
 +ll fastpow(ll x,ll y){
 + ll ret=1;
 + for(;​y;​x=x*x%mod,​y>>​=1)
 + if(y&​1) ret=ret*x%mod;​
 + return ret;
 +}
 +int conv(int x){
 + if(x>​='​a'​) return x-'​a'​+26;​
 + return x-'​A';​
 +}
 +ll dp[N],​fac[N],​inv[N],​temp_dp[N];​
 +ll ans[55][55];​
 +ll w[55];
 +int main(){
 + string s;
 + cin>>​s;​
 + int n=s.length();​
 + fac[0]=1;
 + w[conv(s[0])]++;​
 + for(int i=1;​i<​n;​i++){
 + w[conv(s[i])]++;​
 + fac[i]=fac[i-1]*i%mod;​
 + }
 + fac[n]=fac[n-1]*n%mod;​
 + inv[n]=fastpow(fac[n],​mod-2);​
 + for(int i=n-1;​i>​=0;​i--) inv[i]=inv[i+1]*(i+1)%mod;​
 + ll W=fac[n/​2]*fac[n/​2]%mod;​
 + for(int i=0;​i<​52;​i++){
 + if(w[i]) W=W*inv[w[i]]%mod;​
 + }
 + dp[0]=1;
 + for(int i=0;​i<​52;​i++){
 + if(w[i]){
 + for(int j=n;​j>​=w[i];​j--){
 + dp[j]=(dp[j]+dp[j-w[i]])%mod;​
 + }
 + }
 + }
 + for(int i=0;​i<​52;​i++) ans[i][i]=dp[n/​2];​
 + for(int i=0;​i<​52;​i++){
 + if(!w[i]) continue;
 + for(int k=0;​k<​=n;​k++) temp_dp[k]=dp[k];​
 +
 + for(int k=w[i];​k<​=n;​k++) temp_dp[k]=(temp_dp[k]-temp_dp[k-w[i]]+mod)%mod;​
 + for(int j=i+1;​j<​52;​j++){
 + if(!w[j]) continue;
 + for(int k=w[j];​k<​=n;​k++) temp_dp[k]=(temp_dp[k]-temp_dp[k-w[j]]+mod)%mod;​
 + ans[i][j]=2ll*temp_dp[n/​2]%mod;​
 + ans[j][i]=ans[i][j];​
 + for(int k=n;​k>​=w[j];​k--) temp_dp[k]=(temp_dp[k]+temp_dp[k-w[j]])%mod;​
 + }
 + }
 + int q;
 + scanf("​%d",&​q);​
 + while(q--){
 + int x,y;
 + scanf("​%d%d",&​x,&​y);​
 + int l=conv(s[x-1]);​
 + int r=conv(s[y-1]);​
 + printf("​%lld\n",​W*ans[l][r]%mod);​
 + }
 + return 0;
 } }
 </​code>​ </​code>​
 </​hidden>​ </​hidden>​
2020-2021/teams/legal_string/lgwza/可逆背包.1626419575.txt.gz · 最后更改: 2021/07/16 15:12 由 lgwza