Description
给定一个 \(0/1\) 二进制串 \(s(|s|\le 50)\),和两个整数 \(n,k(n\le 7,k\le 10^5)\)
从不大于将 \(s\) 循环 \(k\) 次后得到的二进制串的数中选不同的 \(n\) 个,异或和为 \(0\) 的方案
Solution
自己口胡的错误解法
题目本质上是要使所有的位置上面满足有偶数个数字相同
同时还要满足所选的 \(n\) 个数得互不相同
正难则反
每个位置上面的方案就是 \(\sum\limits_{i\in \{Even\}}^n\binom n i\)
搞个快速幂,这题就完成了 \(5\%\)
后面是两个瓶颈:
\(1.\) 满足大小关系
满足大小关系的要求就是如果两个数的前几位都一致,那么要在对方是 \(0\) 的位置上面不能是 \(1\)
所以就是考虑每个数对于答案的贡献(被包含在了多少的方案里面)
这一行后面的数字会废掉
\(2.\) 去重
这里上个容斥,把 \(n-1\) 的方案求出来,然后是 \(n-2\) 的 \(\cdots\)
(所以大概数组要开成 \(f_{7,5\times10^6}\) )
正解
好像思路还是有点偏呢,水平还是不行,缺那种 \(dp\) 着定义状态的意识
(暂令 \(R>x_1>x_2>\cdots>x_n\))
用 \(f_{i,st}\) 表示已经从高到低考虑了 \(i\) 位,\(st\) 表示对于当前的位置 \(i\), \(x_i\) 和 \(x_{i-1}\) 的相等关系,如果相等就是 \(1\) ,反之则反之
然后转移的时候直接枚举这一次 \(n\) 个数当前位要填的 \(0/1\)(这里可以状压)
如果满足原来两项相同但是现在后面的比前面的大,那么就不转移了
更新下来状态直接更新答案
这样复杂度显然是不行的,而且显然忽略了题目中对 \(R\) 重复性的描述
所以发现没,又要矩阵快速幂啦
显然对于每个循环的转移方式都是一致的(因为字符串是一样的)
所以口胡就没了
然后进入了长长的 \(coding\ \ time\)
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k==‘-‘) f=-1;
while(isdigit(k)) res=res*10+k-‘0‘,k=getchar();
return res*f;
}
const int mod=1e9+7,N=60,M=2<<7;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int del(int x,int y){return x-y<0?x-y+mod:x-y;}
char s[N];
int n,k,len;
struct node{
int a[M][M];
int* operator [](int x){return a[x];}
inline void init(){memset(a,0,sizeof(a)); return ;}
inline void prework(){for(int i=1;i<=(1<<n);++i) a[i][i]=1; return ;}
};
node ans,base;
int S,f[60][60];
inline node mul(node x,node y)
{
node ans; ans.init();
for(int i=0;i<=S;++i)
{
for(int j=0;j<=S;++j)
{
for(int k=0;k<=S;++k) ans[i][j]=add(ans[i][j],x[i][k]*y[k][j]%mod);
}
} return ans;
}
inline node ksm(node x,int y)
{
node res; res.init(); res.prework();
for(;y;y>>=1,x=mul(x,x)) if(y&1) res=mul(res,x);
return res;
}
int c[M],val[10];
inline int lowbit(int x){return x&(-x);}
inline int calc(int x){int cnt=0; for(;x;x-=lowbit(x)) cnt++; return cnt;}
signed main()
{
n=read(); k=read(); scanf("%s",s+1); len=strlen(s+1); S=(1<<n)-1;
for(int i=0;i<=S;++i) c[i]=calc(i);
for(int st=0;st<=S;++st)//就是状态st向其它的状态进行转移的时候的预处理(大概的样子应该就是初始那列的吧……)
{
memset(f,0,sizeof(f)); f[0][st]=1;
for(int i=1;i<=len;++i)//从高到低前 i 位
{
for(int j=0;j<=S;++j)//上一次到了这种状态
{
if(!f[i-1][j]) continue;
for(int k=0;k<=S;++k)//枚举这一次的几个填数
{
if(c[k]%2!=0) continue;
for(int num=1;num<=n;++num) val[num]=k>>(num-1)&1;
val[0]=s[i]-‘0‘;
for(int num=1;num<=n;++num) cout<<val[num]<<" "; puts("");
int res=0; bool fl=0;
for(int num=1;num<=n;++num)
{
if(j&(1<<(num-1)))
{
if(val[num-1]<val[num]){fl=1; break;}
if(val[num-1]==val[num])res|=1<<(num-1);
}
}if(fl) continue;
f[i][res]=add(f[i][res],f[i-1][j]);
}
}
}
for(int i=0;i<=S;++i) base[st][i]=f[len][i];
}
base=ksm(base,k); printf("%lld\n",base[S][0]);
return 0;
}
}
signed main(){return yspm::main();}
做题随想
原来听说过一句话「选择 \(OI\) 是享受了独立思考的快乐」
我面对这道题,思路确实是偏了
没有往状压那个方向去想
其实那种状态前面也不是没有见过,奇怪的道路和这个不无相似之处
还是不行,感觉最近状态正在转佳呀
我还真是第一次说出「这题爱谁会,谁会吧,我不管了」这种颓废的话
以后不能这样了
思路偏了拧回来就好了不是吗
耐心,耐心,耐心
其实最后写完了发现也就那个样子,根本不是自己
状压题目关键要找到状态比较复杂的点
现在做过的一些题目中主要体现在位的奇偶性和大小关系