四月好题

typewriter

题意

n 个字符集,第 i 个字符集为 Si。可以选择任意一个字符集,然后用这个字符集的字符打出长为 l 的字符串。求最后能够打出多少种字符串。n18,l109,Si 只包含小写字母。

解法

由于 n=18 而字符集大小为 26,故而直接使用容斥的方式计算所有可能的字符串数量即可(比看任一个字符集是否是某个字符集的子集更优)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int md=998244353;
int n,l,i,j,v,p,q,msk;
int m[20],k[20];
char s[30];
ll x;
ll Pow(ll d,int z){
ll ret=1;
do{
if(z&1){ret*=d;if(ret>=md) ret%=md;}
d*=d;if(d>=md) d%=md;
}while(z>>=1);
return ret;
}
int main(){
scanf("%d%d",&n,&l);
for(i=0;i<n;++i){
scanf("%s",s+1);
m[i]=strlen(s+1);
for(j=1;j<=m[i];++j) k[i]|=(1<<(s[j]-'a'));
}
const int siz=(1<<n)-1;
for(i=1;i<=siz;++i){
msk=67108863;v=-1;q=0;
for(p=0;p<n;++p){
if(i&(1<<p)){
msk&=k[p];
v=-v;
}
}
for(;msk;msk^=(msk&-msk)) ++q;
x+=Pow(q,l)*v;
if(x<0) x+=md;
if(x>=md) x-=md;
}
printf("%lld",x);
return 0;
}

Game On Tree 3

题意

有两个人和一棵大小为 n 的树,根节点为 1,非根节点上均有一个非负权值,其中 i 点的权值为 ai。后手有一枚在 1 结点的棋子。这两个人可以在树上轮流操作。

先手每次操作后可以将任意一个节点上的 a 值改为 0,而后手可以将棋子从此节点移动到其任意一个子结点上。

后手可以在其某次移动完棋子后立即结束操作,而在棋子移到叶子节点上时则必须结束操作。

先手会最小化操作结束时棋子所在的节点上的 a 值,后手会最大化这个值。

求先后手采取最优操作后棋子所在处的 a 值。n2105

解法

考虑后手能否走到某个不小于某个值 v 的点。

此时可以令 a 值小于 v 的点作为黑点,其他点作为白点。这样问题转化成先手每次可以将某个白点变黑,而后手需要走到白点上才能取胜,否则先手取胜。

dpi 为先手至少需要在后手到 i 点之前,使得后手不能在 i 树上走到白点时在 i 树上变黑的白点数。显然有 dpi=max(1,jsonidpj)[ai<v]。若后手走到 i 点,先手进行操作后,i 树上变黑的白点数小于 jsonidpj,则先手肯定能找到 i 的一个子节点 j 满足 j 树上变黑的白点数小于 dpj,先手不能取胜。同时若后手在 i 点时,先手能再次在 i 树上变黑一个白点,满足 i 不存在一个子节点 j,其中 j 满足 j 树上变黑的白点数小于 dpj。如果 i 点是白点,则必须事先把 i 点变黑。

如果根节点的 dp 值非 0,则后手一定能走到一个白点上,先手不能必胜;否则先手必胜。

若后手能够到达 a 值不小于 v 的点,则 wv,其一定能到达 a 值不小于 w 的点。可以二分处理,求出答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,u,v,t,s,m,l,r;
int h[maxn],a[maxn],dp[maxn];
struct edge{int to,nxt;}E[maxn<<1];
void dfs(const int &p,const int &f){
int lp,to,sm=0;
for(lp=h[p];lp;lp=E[lp].nxt){
to=E[lp].to;
if(to==f) continue;
dfs(to,p);
sm+=dp[to];
}
--sm;
if(sm<0) sm=0;
if(a[p]>m) ++sm;
dp[p]=sm;
}
int main(){
scanf("%d",&n);
for(i=2;i<=n;++i){
scanf("%d",a+i);
r=max(r,a[i]);
}
for(i=1;i<n;++i){
scanf("%d%d",&u,&v);
E[++t]={v,h[u]};h[u]=t;
E[++t]={u,h[v]};h[v]=t;
}
while(l<r){
m=(l+r)>>1;
dfs(1,0);
if(dp[1]) l=m+1;
else r=m;
}
printf("%d",l);
return 0;
}

01? Queries

题意

有一个长为 n01?s,其中 ? 可以看作 01。同时有 q 次修改,每次修改串中的一个字符,求串中的不同子序列个数模 998244353n,q105

解法

考虑无修改时的做法。

dpi,0 表示 s1si 中以 0 结尾的不同子串个数,dpi,1 表示 s1si 中以 1 结尾的不同子串个数。考虑 dpi1dpi 的转移。

为了方便,我们只考虑 dpi 中的子串均以 si 结尾的情况(如果不以 si 结尾,则可以把结尾换成 si,不会造成其他影响)。显然,若只这样考虑,则 dpi,si 即为 s1si1 的所有不同子串数(包括空串),即 dpi1,0+dpi1,1+1dpi,!si 即为 dpi1,!si。若 si=?,则 dpi,0=dpi,1=dpi1,0+dpi1,1+1

考虑使用矩阵快速幂对修改后的 dp 值进行计算。可以发现可以用矩阵乘法优化如下:

[dpi,0dpi,11]=[dpi1,0dpi1,11]×{[100110101]si=0[110010011]si=1[110110111]si=?

边界条件即为 dp0,0=dp0,1=0。使用线段树维护矩阵即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int md=998244353;
char s[maxn];
int n,q,u,x,_i,_j,_k;
struct seg{
long long mat[3][3];
int l,r,m;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mat(p,a,b) tr[p].mat[a][b]
inline void Assign(const int &p){
if(s[l(p)]=='0'){
mat(p,0,1)=mat(p,0,2)=
mat(p,1,2)=mat(p,2,1)=0;
mat(p,1,0)=mat(p,2,0)=
mat(p,0,0)=mat(p,1,1)=mat(p,2,2)=1;
}
else if(s[l(p)]=='1'){
mat(p,0,2)=mat(p,1,0)=
mat(p,1,2)=mat(p,2,0)=0;
mat(p,0,0)=mat(p,2,2)=
mat(p,0,1)=mat(p,1,1)=mat(p,2,1)=1;
}
else{
mat(p,0,2)=mat(p,1,2)=0;
mat(p,0,0)=mat(p,0,1)=
mat(p,1,0)=mat(p,1,1)=
mat(p,2,0)=mat(p,2,1)=mat(p,2,2)=1;
}
}
inline void Pushup(const int &p){
for(_i=0;_i<3;++_i){
for(_j=0;_j<3;++_j){
mat(p,_i,_j)=mat(ls(p),_i,0)*mat(rs(p),0,_j);
mat(p,_i,_j)+=mat(ls(p),_i,1)*mat(rs(p),1,_j);
mat(p,_i,_j)+=mat(ls(p),_i,2)*mat(rs(p),2,_j);
if(mat(p,_i,_j)>=md) mat(p,_i,_j)%=md;
}
}
}
void Build(const int p,const int l,const int r){
l(p)=l;r(p)=r;
m(p)=(l+r)>>1;
if(l(p)==r(p)){
Assign(p);
return;
}
Build(ls(p),l,m(p));
Build(rs(p),m(p)+1,r);
Pushup(p);
}
void Change(const int p,const int &x){
if(l(p)==r(p)){
Assign(p);
return;
}
if(x<=m(p)) Change(ls(p),x);
else Change(rs(p),x);
Pushup(p);
}
int main(){
scanf("%d%d%s",&n,&q,s+1);
Build(1,1,n);
while(q--){
scanf("%d",&u);
scanf(" %c",s+u);
Change(1,u);
x=mat(1,2,0)+mat(1,2,1);
if(x>=md) x-=md;
printf("%d\n",x);
}
return 0;
}

MinimizOR

题意

有一个长为 n 的数组 a,同时有 q 次询问,每次给出一个区间 [l,r],询问 mini,j[l,r],ijaiajn,q105,0ai<230

解法

可以猜测 a 中的 mini,jai or j 中的 i,j 均在 a 中最小的 log2(maxa)+1 个数中。

证明:设 a 中的每个数均在 [0,2k1) 中可以选前 k 小的数可以得出 mini,jai or j,考虑 a 中每个数均在 [0,2k) 之间能否通过选择前 k+1 小的数得出 mini,jai or j

如果这 k+1 个数全在 [0,2k1) 中,则显然其中有 mini,jai or j。若不全在但是至少有两个在 [0,2k1) 中,则选择在 [0,2k1) 中的数一定有结果。若在 [0,2k1) 内的数不足两个,则必有 mini,jai or j2k1,也就是说 i,jai or j=2k1+((i mod 2k1) or (j mod 2k1)),所以只需要将 a 中每个数模 2k1 的最小的 k 个选出。可以发现这些数中第 2 小到第 k+1 小的数模 2k1 一定是 a 中第 2 小到最大的数模 2k1 的所有值中前 k 小的数,此时再考虑第 1 小的数后,最后 k+1 个数模 2k1 的值一定包含了 a 中所有数模 2k1 的数中前 k 小的数。

所以可以直接使用线段树维护 a,查询时在区间中取前 32 小的数,两两按位或即可求得答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
int t,n,i,j,q,lt,rt,ap,tp,ans;
int a[maxn],st[40];
struct seg{
int l,r,m;
int mn,mp;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mn(p) tr[p].mn
#define mp(p) tr[p].mp
inline void pushup(const int &p){
if(mn(ls(p))<mn(rs(p))){
mn(p)=mn(ls(p));
mp(p)=mp(ls(p));
}
else{
mn(p)=mn(rs(p));
mp(p)=mp(rs(p));
}
}
void build(const int p,const int l,const int r){
l(p)=l;r(p)=r;
m(p)=(l+r)>>1;
if(l==r){
mn(p)=a[l];
mp(p)=l;
return;
}
build(ls(p),l,m(p));
build(rs(p),m(p)+1,r);
pushup(p);
}
void change(const int p,const int &x,const int &v){
if(l(p)==r(p)){
mn(p)=v;
return;
}
if(x<=m(p)) change(ls(p),x,v);
else change(rs(p),x,v);
pushup(p);
}
void query(const int p){
if(lt<=l(p)&&rt>=r(p)){
if(ans>mn(p)){
ans=mn(p);
ap=mp(p);
}
return;
}
if(lt<=m(p)) query(ls(p));
if(rt>m(p)) query(rs(p));
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",a+i);
build(1,1,n);
scanf("%d",&q);
while(q--){
scanf("%d%d",&lt,&rt);
for(i=32;i;--i){
ans=1145141919;
query(1);
if(ans==1145141919) break;
change(1,ap,1145141919);
st[++tp]=ap;
}
ans=1145141919;
for(i=1;i<tp;++i) for(j=i+1;j<=tp;++j) ans=min(ans,a[st[i]]|a[st[j]]);
printf("%d\n",ans);
for(i=1;i<=tp;++i) change(1,st[i],a[st[i]]);
tp=0;
}
}
return 0;
}

Cards

题意

n 张卡片,第 i 张卡片写有 piqip,q 均为 {1,2,,n} 排列。

现在需要选取一些卡片,使得 1,2,,n 均至少在这些卡片之一上。求方案数。n2105

解法

听说 GaryH 在这道题上卡了几乎整整一场比赛

考虑把写有 i,j 的卡片看成是在 ij 之间连接的无向边,则每个点的度均为 2(不考虑形成自环的点),可以把整个情境看成由若干个简单环或自环;选择卡片的方案即为选择若干条边,满足每个点连接的边至少有一条被选。

直接选择边考虑较为困难,考虑先选择所有的边再删去某些边,满足删去的边不能为自环上的边,同时删去的任意两条边不共点。令在一个大小为 n 的简单有标号环上删边的方案为 fn(包括不删)(特别地,令 f1=1),则 f2=3。手推可以猜想:对于 n3,fn=fn1+fn2

证明:令在一条有 n 条边的链上进行类似的删边的方案有 gn 种,则由某条边是否被删则 n3,fn=gn1+gn3,而 gn=gn1+gn2(具体证明可以看下图)。特别地,g0=1

从而 n5,有

fn=gn1+gn3=gn2+gn3+gn4+gn5=(gn2+gn4)+(gn3+gn5)=fn1+fn2

而手推可得 f3=4,f4=7,均满足 fn=fn1+fn2。故而始终有 fn=fn1+fn2 成立。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=200010;
const int md=998244353;
int n,i,d,t,c;
ll ans;
bool vis[maxn];
int h[maxn],p[maxn],q[maxn],f[maxn];
struct edge{int to,nxt;}E[maxn<<1];
void dfs(const int p){
vis[p]=1;++c;
int lp,to;
for(lp=h[p];lp;lp=E[lp].nxt){
to=E[lp].to;
if(vis[to]) continue;
dfs(to);
}
}
int main(){
scanf("%d",&n);
f[0]=1;f[1]=2;f[2]=3;f[3]=4;ans=1;
for(i=4;i<=n;++i){
f[i]=f[i-1]+f[i-2];
if(f[i]>=md) f[i]-=md;
}
f[1]=1;
for(i=1;i<=n;++i){
scanf("%d",&d);
p[d]=i;
}
for(i=1;i<=n;++i){
scanf("%d",&d);
q[d]=i;
}
for(i=1;i<=n;++i){
E[++t]={p[i],h[q[i]]};h[q[i]]=t;
E[++t]={q[i],h[p[i]]};h[p[i]]=t;
}
for(i=1;i<=n;++i){
if(vis[i]) continue;
c=0;dfs(i);
ans*=f[c];
if(ans>=md) ans%=md;
}
printf("%lld\n",ans);
return 0;
}

Narrow Components

题意

有一个 3×n01 矩阵。q 次询问,每次询问一个区间 [l,r],求第 l 列到第 r 列有多少个只有 1 的四连通块。n5105,q3105

解法

考虑将两个矩阵拼在一起会减少多少连通块。

显然如果连接部分存在两边均为 1 的一行,则连通块个数应减少 1。如果连接处的第一行和第三行的 1 均存在,连通块个数应减少 2但是如果某一边的第一行和第三行的 1 本来就在一个连通块里,则连通块个数只需要减少 1

线段树维护上述信息即可。具体维护某个区间两边的 1 是否在一个连通块内可见代码。

p.s. 听说这个题有最多可以处理 10 行的使用线段树套并查集的解法

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=500010;
int n,i,q,lp,rp,msk[maxn];
int l[maxn<<2],r[maxn<<2],m[maxn<<2];
struct seg{
int cnt,lm,rm;
bool cl,cr,c2;
inline seg operator +(const seg &a){
seg tmp;
tmp.lm=lm;tmp.rm=a.rm;
tmp.cnt=cnt+a.cnt;
tmp.c2=c2&&a.c2;
tmp.cl=cl||(c2&&a.cl);
tmp.cr=a.cr||(a.c2&&cr);
if(rm&a.lm){
--tmp.cnt;
if((((rm==5)&&(!cr))&&(((a.lm==5)&&(!a.cl))||a.cl))||
(cr&&((a.lm==5)&&(!a.cl)))) --tmp.cnt;
}
return tmp;
}
}tr[maxn<<2],ans;
#define ls(p) p<<1
#define rs(p) p<<1|1
void build(const int p,const int &lt,const int &rt){
l[p]=lt;r[p]=rt;
m[p]=(lt+rt)>>1;
if(lt==rt){
tr[p].lm=tr[p].rm=msk[lt];
if(!msk[lt]) tr[p].cnt=0;
else if(msk[lt]==5){
tr[p].cnt=2;
tr[p].c2=1;
}
else{
tr[p].cnt=1;
if(msk[lt]==7) tr[p].cl=tr[p].cr=1;
}
return;
}
build(ls(p),lt,m[p]);
build(rs(p),m[p]+1,rt);
tr[p]=tr[ls(p)]+tr[rs(p)];
}
void query(const int p,const int &lt,const int &rt){
if(lt<=l[p]&&rt>=r[p]){
if(!ans.cnt) ans=tr[p];
else ans=ans+tr[p];
return;
}
if(lt<=m[p]) query(ls(p),lt,rt);
if(rt>m[p]) query(rs(p),lt,rt);
}
int main(){
scanf("%d",&n);
getchar();
for(i=1;i<=n;++i){
q=getchar()^'0';
if(q) msk[i]|=1;
}
getchar();
for(i=1;i<=n;++i){
q=getchar()^'0';
if(q) msk[i]|=2;
}
getchar();
for(i=1;i<=n;++i){
q=getchar()^'0';
if(q) msk[i]|=4;
}
build(1,1,n);
scanf("%d",&q);
while(q--){
scanf("%d%d",&lp,&rp);
ans={0,0,0,0,0,0};
query(1,lp,rp);
printf("%d\n",ans.cnt);
}
}

Teleporters

题意

有一个有 n 个正整数的集合,第 i 个数为 aiai1a0=0)。现在可以进行若干次操作,每次操作可以取出一个数 x,然后把 yxy 两个正整数放回集合。求使得集合内所有数的平方和不大于 M 的最小操作次数。n2105,ai109,anM1018

解法

考虑将某个数 s 拆分 k 次对答案的最小贡献。

显然若其中拆出的数有 pp+2+k(k0),则有

p2+(p+2+k)2=p2+(p+k)2+4(p+k)+4=(p2+2p+1)+((p+k)2+2(p+k)+1)+2(k+1)=(p+1)2+(p+1+k)2+2(k+1)>(p+1)2+(p+1+k)2

故而最优方案中,最后拆出来的数一定不会有相差大于 2 的数,也就是拆出的数一定是 smodksk+1k(smodk)sk,贡献即为

=  (smodk)(sk+1)2+(k(smodk))sk2=(smodk)(sk2+2sk+1)+(k(smodk))sk2=ksk2+(sksk)(2sk+1)=ksk22ksk2ksk+2ssk+s=ksk2+(2sk)sk+s

sk=p,fs(k)=ksk2+(2sk)sk+s,则在对应一段的斜率为 p2p,而之后的满足 sk=p1 的一段的斜率为 (p1)2(p1)=p2+2p1p+1=p2+p 大于 p2p。故而对于固定的 s,关于 k 的函数 fs(k) 是一段下凸函数。可以使用堆维护每个 fs(k) 目前的斜率,每次在 fs(k) 对应的斜率最小的 s 贪心进行操作即可。

注意:fs(k)kN+,不能单纯地按上述公式计算斜率,而需要在斜率交界处重新计算 fs(k+1)fs(k)

代码

注意下面 Calc 函数的效率。

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int INF=2147483647;
#define ll long long
int n,i,c,l,r,d,cnt;
int a[maxn];
ll m,v,ans;
inline ll Calc(const int &x,const int &y){
const ll p=x/(y+1);
return p*p*(y+1)+(x-(y+1)*p)*(2*p+1);
}
inline int got(const int &x,const int &y){
if(x<=y) return INF;
return x/(x/(y+1));
}
struct node{
int len,use;ll val,vax;
inline bool operator <(const node &a)const{
return vax<a.vax;
}
}tmp;
priority_queue<node> q;
int main(){
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",a+i);
scanf("%lld",&m);
for(i=n;i;--i){
a[i]-=a[i-1];ans+=1LL*a[i]*a[i];
q.push((node){a[i],0,1LL*a[i]*a[i],1LL*a[i]*a[i]-Calc(a[i],1)});
}
while(ans>m){
tmp=q.top();q.pop();
c=got(tmp.len,tmp.use);
if(c!=tmp.use+1) --c;
v=Calc(tmp.len,c);
if(ans-tmp.val+v>=m){
cnt+=c-tmp.use;
tmp.use=c;
ans-=tmp.val-v;
tmp.val=v;
tmp.vax=v-Calc(tmp.len,c+1);
q.push(tmp);
}
else{
l=tmp.use;r=c;
while(l<r){
d=(l+r)>>1;
if(ans-tmp.val+Calc(tmp.len,d)<=m) r=d;
else l=d+1;
}
cnt+=l-tmp.use;
break;
}
}
printf("%d",cnt);
return 0;
}

Rotate and Play Game

题意

有一个长为 n 的序列 a。两个人轮流进行若干次操作:先手可以取走 a 中任意元素,然后后手取走 a 中下标最小的元素,直到把序列取空。操作前可以将 a{a1,a2,,an} 变为 {ak+1,ak+2,,an,a1,a2,,ak},求先手能取到的数的和的最大值和对应的任意一个合法 kn2105,ai109n 为偶数。

解法

猜想一定存在一种方案使得先手能够取到前 n2 大的数。

证明:考虑把前 n2 大的数看成是 1,其他数看成是 1,则序列的前缀和均不小于 0 时先手能取遍前 n2 大的数。此时先手可以取第一个 1(令其所在的位置为第 p 个),此时肯定仍有序列的前缀和均不小于 0,也就是当前序列的第一个数为 1。而后手取走这个 1 后,第 p1 个数及以后的所有数均未发生改变;这之前的数(如果存在)肯定均为 1,且它们的和同样不变。

至于如何构造这样的序列,可以求出序列中最小的前缀和对应的前缀(令其为 1k 且这个最小前缀和为 S),则 k 即为所求答案,把序列 a={a1,a2,,an} 变成 a={ak+1,ak+2,,an,a1,a2,,ak} 即满足上述性质。由于 j[k+1,n],i=k+1jai=i=1jaii=1kai0,且 i=k+1nai=i=1kai=S,则 b[1,k],i=k+1nai+i=1baiS+S=0。故而这样构造有其前缀和均不小于 0

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,d,x;
int a[maxn],p[maxn];
long long s;
inline bool cmp(const int &y,const int &z){return a[y]<a[z];}
int main(){
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d",a+i);
p[i]=i;
}
x=n;
sort(p+1,p+n+1,cmp);
n>>=1;
for(i=1;i<=n;++i) a[p[i]]=1;
n<<=1;
while(i<=n){
s+=a[p[i]];
a[p[i]]=-1;
++i;
}
for(i=1;i<n;++i){
a[i]+=a[i-1];
if(a[i]<a[x]) x=i;
}
if(x==n) x=0;
printf("%d %lld",x,s);
return 0;
}

P7949 左方之地 & ARC138D Differ by K Bits

题意

要求构造一个 02n1 的排列 P,满足相邻两个数的异或值在二进制的表示下刚好有且只有 k1。求任意一种构造方案,或判断其为无解。ARC138D:需要最后一个数和第一个数的异或值满足同样的性质。

P7949:n,k20。ARC138D:kn18

解法

首先 k 为偶数时(考虑奇偶性)或不小于 n 时显然无解。

考虑格雷码(k=1 的情况)。长为 n 的格雷码 SO(2n) 的构造方式:第 i 项为 ii2

证明:

如果 i 为偶数,则 (i(i+1))2 只有一个 1,而 i2=i+12,故而有 SiSi+1=1

如果 i 为奇数,且 (i+1)2 末尾有 c0(显然第 c+1 位为 1),令 k=i2c+1,则 i+1=(k2c+1)2ci+12=(k2c)2c1i=k2c+1(j=0c12j)i2=k2c(j=0c22j)。则 Si+1=(k2c+1)2c(k2c)2c1Si=(k2c+1)(j=0c12j)(k2c)(j=0c22j)=(k2c+1)(k2c)2c1;从而 SiSi+1=2c

同时若 i,j,且 Si=Sj,则 ii2=jj2,故而 ij=i2j2。同时 ij=2(i2j2)+((i mod 2)(j mod 2)),故而 (i2j2)+((i mod 2)(j mod 2))=0,也就是 i2=j2(i mod 2)=(j mod 2)。从而这样只有 i=j 才有 Si=Sj

综上,S 满足 ij,SiSj(SiSi+1)2 只有一个 1

所以我们有了对于任意的 n 构造 k=1 的解法。同时考虑构造 k=n1 的方案。发现如果把奇数位的 S 值的所有位取反,则对于取反的 Si(Si1Si)2(Si+1Si)2 均有 n11。同时 (S2n1S0)2 有同样的性质。

考虑从 n=k+i 的合法解推到 n=k+i+1 的合法解。可以构造 S2k+i1+j=S2k+ij(2k11)2k+i,此时 (S2k+iS2k+i1)2k1,而 S2k+i+p1S2k+i+p=(S2k+ip((2k11)2k+i))(S2k+ip1((2k11)2k+i)=S2k+ipS2k+ip1,同样在二进制表达下有 k1。新的 S2n1S0 同理。同时若 a,b2k+i,则 Sa=SbS2k+i+11a((2k11)2k+i)=S2k+i+11b((2k11)2k+i),也就是需要 S2k+i+11a=S2k+i+11b,而此条件满足当且仅当 a=b。综上,这样的构造方法可以得到 n=k+i+1 的合法解。

综上,可以先求 n=k+1 的合法解,然后逐级递推,即可求得答案。

代码

点此查看代码
//P7949
#include <bits/stdc++.h>
using namespace std;
int n,k,s,i,j,l,q;
int b[1048600];
int main(){
scanf("%d%d",&n,&k);
q=k;
if(n==1&&k==1){
printf("1\n0 1");
return 0;
}
if(k>=n||(!(k&1))){
printf("0\n");
return 0;
}
printf("1\n");
s=(1<<(k+1))-1;
for(i=0;i<=s;++i){
b[i]=i^(i>>1);
if(i&1) b[i]^=s;
}
l=s;s=(1<<(k-1))-1;++k;
while(k<n){
j=l+1;
for(i=l;i>=0;--i) b[j++]=(b[i]+(1<<k))^s;
l=l<<1|1;++k;
}
for(i=0;i<=l;++i) printf("%d ",b[i]);
return 0;
}

下述内容待补

Decreasing Subsequence

题意

求最长下降子序列长度不小于 k 的长为 n 的序列 a 的个数,满足 i[1,n],ai[0,i]i,j[1,n],ijai=aj0n5000

解法

代码

点此查看代码

Beautiful Subsequences

题意

给出一个 1n 的排列 P,求 P 中有多少个区间 [l,r] 满足 maxj=lrPjminj=lrPjrl+kn1.4105,0k3

解法

代码

点此查看代码

Edge Elimination

题意

给出一棵大小为 n 的无根树,可以进行若干次操作,每次操作删除一条与偶数条边相邻的边。求一种删除所有边的合法次序,或判断其为无解。n2105

解法

代码

点此查看代码

AND-MEX Walk

题意

给定一个 n 个节点 m 条边的无向简单连通图,边有边权。我们定义一条路径(即可以重复经过同一个节点或同一条边的路径)的权值如下:设该途径按顺序经过的边的权值为 w1,w2,w3,,则该途径的权值为 mexi=1{&j=1iwj}。给定 q 次询问,每次给定两个不同的整数 u,v,求所有从节点 u 开始到节点 v 结束的路径中,路径权值的最小值。2n105,0w<230,1q105

解法

代码

点此查看代码

Checker for Array Shuffling

题意

给定一个长为 n 的数组 a。若 a 的一个排列 b 可以通过 k 次将任意两个数交换位置得到 a,则 b 的价值为所有合法 k 的最小值。现在给出 a 的某个排列 b,判断它的价值是否为所有长为 n 的数组的最大值。若是则输出 AC ,否则输出 WAt 组数据。n2105,1ai,bin.

解法

代码

点此查看代码

Cross Xor

题意

有一个 r×c 的全 0 矩阵 a,和一个 r×c01? 矩阵 b。可以进行若干次操作,每次操作可以指定 ij,然后将 ax,y(x=iy=j) 异或上 1。求 a 最终能变成的矩阵 c 中,有多少个满足 bx,y?,cx,y=bx,yr,c2000

解法

代码

点此查看代码

Zigu Zagu

题意

你有一个长度为 n01a。现在给出 q 次询问,每次询问给出两个数 l,r,保证 1lrn。令 s=alar 你可以对 s 做如下操作:

  1. 选择两个数 x,y,满足 1xy|s|。令子串 t=sxsy,对于所有的 1i|t|1,需要满足 titi+1

  2. 删除 s[x,y]

对于每一个询问,请求出最少需要多少个操作才能把 s 变成一个空串。n,q2105

解法

代码

点此查看代码

Xor Cards

题意

n 张卡片,第 i 张卡片上写有 ai,bi。现在要选出若干卡片(记选了 {c1,,cm}(m1))且保证 j=1macjK 时,求 maxj=1mbcj,或判断不存在选出卡片的合法方案。n1000,ai,bi230

解法

代码

点此查看代码

Antennas

题意

1n 的数轴上有 n 座信号塔,每个整点上有一座。第 i 座信号塔和第 j 座信号塔可以传递信息当且仅当 |ij|min(pi,pj)。求从 ab 传递信息经过的最少信号塔数(不包括 a)。n2105

解法

代码

点此查看代码
posted @   Fran-Cen  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示