话说我怎么这么菜啊,做过的题还是不会做!
以前写的题解
补充:
怎么从限制想到将操作序列转成01的呢?
设 ∣ 0 , & 1 |0,\&1 ∣0,&1为无用操作, ∣ 1 , & 0 |1,\&0 ∣1,&0为有用操作,若这一位最后得到了 1 1 1,则必然最后一个操作是 ∣ 1 |1 ∣1——也就是只比较最后一个有用的位——类似于字典序大小比较(第一个不同的位),然后就可以联想到 ∣ → 0 , & → 1 |\to 0,\& \to 1 ∣→0,&→1了。
发现枚举最后一个有用操作无法兼顾每一位,这时必须要换一种思路。
#include
using namespace std;
const int N=5005,mod=1e9+7;
int n,m,q,bin[N],rk[N],c[N],cnt[N];
int g[2][N];
char s[1002][N],t[N];
inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void dc(int &x,int y){x-=y;if(x<0) x+=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
int i,j,k,a,b,L,R;bin[0]=1;
scanf("%d%d%d",&n,&m,&q);
for(i=1;i<=n;++i) bin[i]=inc(bin[i-1],bin[i-1]);
for(i=1;i<=n;++i){
scanf("%s",s[i]+1);
for(j=1;j<=m;++j) s[i][j]-='0';
}
for(i=1;i<=m;++i) c[i]=i;
for(i=1;i<=n;++i){
g[0][0]=g[1][0]=0;
for(j=1;j<=m;++j){
k=s[i][c[j]];
g[k][++g[k][0]]=c[j];
}
a=g[0][0];b=g[1][0];
for(j=1;j<=a;++j) rk[j]=g[0][j];
for(j=1;j<=b;++j) rk[a+j]=g[1][j];
memcpy(c,rk,(m+1)<<2);
}
for(i=1;i<=m;++i)
rk[c[i]]=i;
for(i=1;i<=m;++i)
for(j=1;j<=n;++j)
if(s[j][c[i]]) ad(cnt[i],bin[j-1]);
cnt[m+1]=bin[n];
for(;q;--q){
L=0;R=m+1;scanf("%s",t+1);
for(i=1;i<=m;++i)
if(t[i]=='1') R=min(R,rk[i]);
else L=max(L,rk[i]);
if(L>=R) puts("0");
else printf("%d\n",dec(cnt[R],cnt[L]));
}
fclose(stdin);fclose(stdout);
return 0;
}
以前写的题解
十分详细
后一部分单调栈转成后缀max再转线段树的操作还是觉得非常妙(用多一个 log \log log的代价维护除了区间答案)
#include
#define mid (l+r>>1)
#define lc k<<1
#define rc k<<1|1
#define lcc lc,l,mid
#define rcc rc,mid+1,r
using namespace std;
const int N=2e5+10;
int n,m,ans,lim,typ,t[N];
int mx[N<<2],val[N<<2];
int upd(int k,int v,int l,int r)
{
if(l==r) return l+max(v,mx[k]);
if(v>=mx[k]) return l+v;
if(v>=mx[rc]) return min(mid+1+v,upd(lc,v,l,mid));
return min(val[k],upd(rc,v,mid+1,r));
}
inline void pu(int k,int l,int r)
{
val[k]=upd(lc,mx[rc],l,mid);
mx[k]=max(mx[lc],mx[rc]);
}
void build(int k,int l,int r)
{
if(l==r) {val[k]=l+t[l];mx[k]=t[l];return;}
build(lcc);build(rcc);pu(k,l,r);
}
void chg(int k,int l,int r,int pos,int v)
{
if(l==r) {val[k]=l+v;mx[k]=v;return;}
if(pos<=mid) chg(lcc,pos,v);else chg(rcc,pos,v);
pu(k,l,r);
}
int main(){
freopen("circle.in","r",stdin);
freopen("circle.out","w",stdout);
int i,j,x,y;scanf("%d%d%d",&n,&m,&typ);
for(i=1;i<=n;++i) {scanf("%d",&j);t[i]=j-i;t[i+n]=j-i-n;}
lim=(n<<1);build(1,1,lim);
ans=val[1]+n-1;printf("%d\n",ans);
for(;m;--m){
scanf("%d%d",&x,&y);if(typ) x^=ans,y^=ans;
chg(1,1,lim,x,y-x);chg(1,1,lim,x+n,y-x-n);
ans=val[1]+n-1;printf("%d\n",ans);
}
fclose(stdin);fclose(stdout);
return 0;
}
也不算太毒瘤吧,至少部分分很多。
设 f [ x ] [ 1 / 0 ] f[x][1/0] f[x][1/0]分别表示 x x x选或不选时的子树方案和。
比较容易想到枚举 2 m − n + 1 2^{m-n+1} 2m−n+1枚举每条非树边的选择状态——对于 ( u , v ) (u,v) (u,v),枚举 u u u是否选择:若选择则 v v v必须不选,否则 v v v可以选也可以不选。 O ( 2 10 n ) O(2^{10}n) O(210n)已经可以过掉大部分点了。
考虑把与这 10 10 10条边相关的20个点提出来做虚树,实际上在枚举状态时虚数外部的DP几乎是重复的:对于虚树上的一条边 ( u , v ) (u,v) (u,v)(设 u u u为父亲),可以把非虚树点的DP值都看成常数,那么 f [ u ] [ 1 / 0 ] f[u][1/0] f[u][1/0]必然可以表现成 x ⋅ f [ v ] [ 0 ] + y ⋅ f [ v ] [ 1 ] x·f[v][0]+y·f[v][1] x⋅f[v][0]+y⋅f[v][1]的形式,其中 x , y x,y x,y为常数系数。
O ( n ) O(n) O(n)DP一遍求出虚树上每个点向父亲转移的常数系数。然后还是 2 m − n + 1 2^{m-n+1} 2m−n+1枚举状态在虚树上DP。
此题比较口胡,具体实现非常有技巧性,建议代码实现一下。
具体见代码(几乎是照着这篇写的,个人感觉写得很好)
#include
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+50,mod=998244353;
int n,m,df[N],dfn,sn[N],cnt;
int cn[N][2],wb[N][2],f[N][2],ans;
int head[N],to[N<<1],nxt[N<<1],tot;
bool in[N],vs[N];
inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void dc(int &x,int y){x-=y;if(x<0) x+=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
struct ob{
int x,y;
ob(int x_=0,int y_=0):x(x_),y(y_){};
ob operator +(const ob&ky){return ob(inc(x,ky.x),inc(y,ky.y));}
ob operator *(const int &ky){return ob((ll)x*ky%mod,(ll)y*ky%mod);}
}le[N],nb[N][2];
struct dr{int v;ob a,b;};
vector<dr>g[N];
inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
int dfs(int x,int fr)
{
int i,j;df[x]=++dfn;
for(i=head[x];i;i=nxt[i]){
j=to[i];if(j==fr) continue;
if(!df[j]) {sn[x]+=dfs(j,x);continue;}
in[x]=true;
if(df[x]<df[j]) le[++cnt]=ob(x,j);
}
if(sn[x]>1) in[x]=true;
return (in[x] || sn[x]>0)?1:0;
}
int mk(int x)
{
int re=0,res,v,i,j;
wb[x][0]=wb[x][1]=1;vs[x]=true;
for(i=head[x];i;i=nxt[i]){
j=to[i];if(vs[j]) continue;
res=mk(j);
if(!res){
wb[x][0]=(ll)wb[x][0]*inc(wb[j][0],wb[j][1])%mod;
wb[x][1]=(ll)wb[x][1]*wb[j][0]%mod;
}else if(in[x]) g[x].pb((dr){res,nb[j][0]+nb[j][1],nb[j][0]});
else nb[x][0]=nb[j][0]+nb[j][1],nb[x][1]=nb[j][0],re=res;
}
if(in[x]) nb[x][0]=ob(1,0),nb[x][1]=ob(0,1),re=x;
else nb[x][0]=nb[x][0]*wb[x][0],nb[x][1]=nb[x][1]*wb[x][1];
return re;
}
void cal(int x)
{
int i,j,a,b;dr tp;
f[x][0]=cn[x][1]?0:wb[x][0];f[x][1]=cn[x][0]?0:wb[x][1];
for(i=g[x].size()-1;i>=0;--i){
tp=g[x][i];cal(tp.v);a=f[tp.v][0];b=f[tp.v][1];
f[x][0]=(ll)f[x][0]*inc((ll)tp.a.x*a%mod,(ll)tp.a.y*b%mod)%mod;
f[x][1]=(ll)f[x][1]*inc((ll)tp.b.x*a%mod,(ll)tp.b.y*b%mod)%mod;
}
}
int main(){
freopen("duliu.in","r",stdin);
freopen("duliu.out","w",stdout);
int i,j,x,y,S,num;in[1]=true;
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i){scanf("%d%d",&x,&y);lk(x,y);lk(y,x);}
dfs(1,0);mk(1);S=1<<cnt;
for(i=0;i<S;++i){
//cn[i][1/0]=1 勒令 i 选/不选
for(j=0;j<cnt;++j)
if((i>>j)&1) cn[le[j+1].x][1]=cn[le[j+1].y][0]=1;
else cn[le[j+1].x][0]=1;
cal(1);ad(ans,inc(f[1][0],f[1][1]));
for(j=0;j<cnt;++j)
if((i>>j)&1) cn[le[j+1].x][1]=cn[le[j+1].y][0]=0;
else cn[le[j+1].x][0]=0;
}
printf("%d",ans);
fclose(stdin);fclose(stdout);
return 0;
}
总结
T1可能智障选手已经想不出正解了,暴力无从下手?
T2最后一步也大概是想不出来了
T3信仰暴力,虚树求系数以目前代码能力估计敲不出来了
三题暴力告辞