NOI2021 题解
[NOI2021] 轻重边
转化一下题意:每次给一条链染色,查一条链从 \(x\) 到 \(y\) 有几条边两端颜色相同。那这个随便树剖线段树就好了。也可以 LCT,码量可能要小点。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
int n,m;
struct gra{
int v,next;
}edge[200010];
int t,head[100010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int num,dfn[100010],rk[100010],son[100010],size[100010],fa[100010],top[100010],dep[100010];
void dfs1(int x,int f){
size[x]=1;dep[x]=dep[f]+1;fa[x]=f;
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f){
dfs1(edge[i].v,x);
size[x]+=size[edge[i].v];
if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
}
}
}
void dfs2(int x,int f,int tp){
dfn[x]=++num;rk[num]=x;top[x]=tp;
if(son[x])dfs2(son[x],x,tp);
for(int i=head[x];i;i=edge[i].next){
if(edge[i].v!=f&&edge[i].v!=son[x])dfs2(edge[i].v,x,edge[i].v);
}
}
struct data{
int l,r,sum;
friend data operator+(data s,data t){
if(s.l==0&&s.r==0)return t;
if(t.l==0&&t.r==0)return s;
data ans;
ans.l=s.l,ans.r=t.r;
ans.sum=s.sum+t.sum+(s.r==t.l);
return ans;
}
};
struct node{
#define lson rt<<1
#define rson rt<<1|1
data val;
int len,lz;
}tree[400010];
void pushup(int rt){
tree[rt].val=tree[lson].val+tree[rson].val;
}
void pushtag(int rt,int val){
tree[rt].val.l=tree[rt].val.r=val;tree[rt].val.sum=tree[rt].len-1;tree[rt].lz=val;
}
void pushdown(int rt){
if(tree[rt].lz){
pushtag(lson,tree[rt].lz);
pushtag(rson,tree[rt].lz);
tree[rt].lz=0;
}
}
void build(int rt,int l,int r){
tree[rt].len=r-l+1;
tree[rt].val={0,0,0};tree[rt].lz=0;
if(l==r){
tree[rt].val={l,l,0};return;
}
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(rt);
}
void update(int rt,int L,int R,int l,int r,int val){
if(l<=L&&R<=r){
pushtag(rt,val);return;
}
pushdown(rt);
int mid=(L+R)>>1;
if(l<=mid)update(lson,L,mid,l,r,val);
if(mid<r)update(rson,mid+1,R,l,r,val);
pushup(rt);
}
data query(int rt,int L,int R,int l,int r){
if(l<=L&&R<=r)return tree[rt].val;
pushdown(rt);
int mid=(L+R)>>1;data val={0,0,0};
if(l<=mid)val=val+query(lson,L,mid,l,r);
if(mid<r)val=val+query(rson,mid+1,R,l,r);
return val;
}
void change(int x,int y,int val){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
update(1,1,n,dfn[top[x]],dfn[x],val);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
update(1,1,n,dfn[x],dfn[y],val);
}
int query(int x,int y){
data L={0,0,0},R={0,0,0};
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]]){
data ret=query(1,1,n,dfn[top[x]],dfn[x]);
L=ret+L;x=fa[top[x]];
}
else{
data ret=query(1,1,n,dfn[top[y]],dfn[y]);
R=ret+R;y=fa[top[y]];
}
}
if(dep[x]<dep[y]){
swap(L.l,L.r);
data ret=query(1,1,n,dfn[x],dfn[y]);
L=L+ret+R;
return L.sum;
}
else{
swap(R.l,R.r);
data ret=query(1,1,n,dfn[y],dfn[x]);
R=R+ret+L;
return R.sum;
}
}
int main(){
int tim;scanf("%d",&tim);
while(tim--){
scanf("%d%d",&n,&m);t=num=0;
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs1(1,0);dfs2(1,0,1);
build(1,1,n);
int cnt=n;
while(m--){
int od,u,v;scanf("%d%d%d",&od,&u,&v);
if(od==1){
cnt++;change(u,v,cnt);
}
else printf("%d\n",query(u,v));
}
for(int i=1;i<=n;i++)head[i]=son[i]=0;
}
return 0;
}
[NOI2021] 路径交点
LGV 板子。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int mod=998244353;
int n,k,a[110][110],sum[110],m[110];
struct node{
int v,next;
}edge[4000010];
int t,head[20010];
void add(int u,int v){
edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int cnt[20010];
bool vis[20010];
void bfs(int st){
queue<int>q;
for(int i=1;i<=sum[k];i++)cnt[i]=0,vis[i]=false;
q.push(st);cnt[st]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=edge[i].next){
cnt[edge[i].v]=cnt[edge[i].v]+cnt[x];
if(cnt[edge[i].v]>=mod)cnt[edge[i].v]-=mod;
if(!vis[edge[i].v])q.push(edge[i].v),vis[edge[i].v]=true;
}
}
}
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
int gauss(int n){
int ans=1,w=1;
for(int i=1;i<=n;i++){
int mx=i;
for(int j=i+1;j<=n;j++){
if(a[j][i]>a[mx][i])mx=j;
}
if(mx!=i){
w=-w;
for(int j=1;j<=n;j++)swap(a[i][j],a[mx][j]);
}
if(!a[i][i])return 0;
int inv=qpow(a[i][i],mod-2);
for(int j=1;j<=n;j++){
if(i==j)continue;
int rate=1ll*a[j][i]*inv%mod;
for(int k=1;k<=n;k++)a[j][k]=(a[j][k]-1ll*rate*a[i][k]%mod+mod)%mod;
}
}
for(int i=1;i<=n;i++)ans=1ll*ans*a[i][i]%mod;
ans=(ans*w+mod)%mod;
return ans;
}
int main(){
int tim;scanf("%d",&tim);
while(tim--){
scanf("%d",&k);t=0;
for(int i=1;i<=k;i++)scanf("%d",&sum[i]),sum[i]+=sum[i-1];
for(int i=1;i<k;i++)scanf("%d",&m[i]);
for(int i=1;i<k;i++){
while(m[i]--){
int u,v;scanf("%d%d",&u,&v);
add(sum[i-1]+u,sum[i]+v);
}
}
for(int i=1;i<=sum[1];i++){
bfs(i);
for(int j=1;j<=sum[1];j++)a[i][j]=cnt[sum[k-1]+j];
}
printf("%d\n",gauss(sum[1]));
for(int i=1;i<=sum[k];i++)head[i]=0;
}
return 0;
}
[NOI2021] 庆典
首先缩点。然后根据这图的性质一定能找一个拓扑序是一棵树,在最后一条入边连上去即可。于是变成了树上问题。\(k=0,1\) 就显然了。
然后是 \(k\) 更大的情况。可以建虚树,然后变成点有点权边有边权,从 \(s\) 到 \(t\) 的路径并。点数是 \(O(k)\) 的,爆扫一遍就行了。
[NOI2021] 量子通信
因为 \(k\le 15\) 所以拆 \(16\) 段一定有一个相同。然后因为数据随机在某一位为某个数的期望个数是 \(\dfrac n{2^{16}}\approx 6\)。于是暴力扫就行了。
挺卡常的。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
typedef unsigned long long ull;
namespace IO{
char buf[1<<23],*p1=buf,*p2=buf;
#define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
inline int read(){
int x=0;char s=gc;
while(!isdigit(s))s=gc;
while(isdigit(s))x=(x<<1)+(x<<3)+(s-'0'),s=gc;
return x;
}
}
using namespace IO;
int n,m,K;
ull a,b;
ull myRand(ull &k1,ull &k2){
ull k3=k1,k4=k2;
k1=k4;
k3^=(k3<<23);
k2=k3^k4^(k3>>17)^(k4>>26);
return k2+k4;
}
unsigned short s[400010][16];
ull S[400010][4];
vector<int>v[16][1<<16];
void gen(int n,ull a1,ull a2){
for(int i=1;i<=n;i++){
for(int j=0;j<16;j++){
for(int k=15;k>=0;k--)s[i][j]|=((myRand(a1,a2)&(1ull<<32))?1:0)<<k;
v[j][s[i][j]].push_back(i);
}
for(int j=0;j<4;j++){
S[i][j]|=(ull)s[i][j*4]<<48;
S[i][j]|=(ull)s[i][j*4+1]<<32;
S[i][j]|=(ull)s[i][j*4+2]<<16;
S[i][j]|=(ull)s[i][j*4+3];
}
}
}
bool lastans;
char ch[110];
unsigned short od[256],tmp[16];
ull TMP[4];
bool check(int id){
int ans=0;
for(int i=0;i<4;i++){
ans+=__builtin_popcountll(TMP[i]^S[id][i]);
if(ans>K)return false;
}
return true;
}
void solve(){
for(int i=0;i<64;i++){
unsigned short num;
char ch=gc;
if(isdigit(ch))num=ch-'0';
else if(ch>='A'&&ch<='F')num=10+ch-'A';
else {i--; continue;}
for(int j=3;j>=0;j--){
od[(i+1)*4-j-1]=((num>>j)&1)^lastans;
}
}
K=read();
for(int i=0;i<16;i++){
tmp[i]=0;
for(int j=15;j>=0;j--)tmp[i]|=od[(i+1)*16-j-1]<<j;
}
for(int i=0;i<4;i++){
TMP[i]=0;
TMP[i]|=(ull)tmp[i*4]<<48;
TMP[i]|=(ull)tmp[i*4+1]<<32;
TMP[i]|=(ull)tmp[i*4+2]<<16;
TMP[i]|=(ull)tmp[i*4+3];
}
for(int i=0;i<16;i++){
for(int x:v[i][tmp[i]]){
if(check(x)){
lastans=1;cout<<1<<'\n';
return;
}
}
}
lastans=0;cout<<0<<'\n';
}
int main(){
cin>>n>>m>>a>>b;
gen(n,a,b);
while(m--)solve();
return 0;
}
[NOI2021] 密码箱
首先这题的 key observation 是 \(f\) 是连分数,然后连分数的每个渐进分数的分子分母都是互质的而且可以递推。具体递推式:设第 \(k\) 项为 \(p_k,q_k\),则有
看到递推想到矩阵维护,每项的矩阵就是 \(\left(\begin{matrix}a_k & 1\\1 & 0\end{matrix}\right)\)。看两种修改:
W
:显然 \(\left(\begin{matrix}1 & 0\\1 & 1\end{matrix}\right)\)。E
:不是很显然。第一个操作是左乘个W
的矩阵,第二个是\[\left(\begin{matrix}1 & 0\\1 & 1\end{matrix}\right)^{-1}\left(\begin{matrix}1 & 1\\1 & 0\end{matrix}\right)\left(\begin{matrix}1 & 1\\1 & 0\end{matrix}\right)=\left(\begin{matrix}2 & 1\\-1 & 0\end{matrix}\right) \]然后可以发现在 \(a_k=1\) 时两种操作是等价的,于是可以直接看做 \(\left(\begin{matrix}2 & 1\\-1 & 0\end{matrix}\right)\)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int mod=998244353;
int n,q;
char s[200010];
struct Mat{
int a[2][2];
Mat(){memset(a,0,sizeof(a));}
Mat operator*(const Mat&s)const{
Mat ret;
ret.a[0][0]=(1ll*a[0][0]*s.a[0][0]+1ll*a[0][1]*s.a[1][0])%mod;
ret.a[0][1]=(1ll*a[0][0]*s.a[0][1]+1ll*a[0][1]*s.a[1][1])%mod;
ret.a[1][0]=(1ll*a[1][0]*s.a[0][0]+1ll*a[1][1]*s.a[1][0])%mod;
ret.a[1][1]=(1ll*a[1][0]*s.a[0][1]+1ll*a[1][1]*s.a[1][1])%mod;
return ret;
}
}P,W,E;
struct node{
#define lson tree[x].son[0]
#define rson tree[x].son[1]
Mat val[2],sum[2][2];
int size,data,son[2];
bool fl,rev;
}tree[200010];
int t,rt,x,y,z;
int New(int x){
tree[++t].size=1;tree[t].data=rand();
if(x==0){
tree[t].val[0]=tree[t].sum[0][0]=tree[t].sum[0][1]=W;
tree[t].val[1]=tree[t].sum[1][0]=tree[t].sum[1][1]=E;
}
else{
tree[t].val[0]=tree[t].sum[0][0]=tree[t].sum[0][1]=E;
tree[t].val[1]=tree[t].sum[1][0]=tree[t].sum[1][1]=W;
}
return t;
}
void pushup(int x){
tree[x].size=tree[lson].size+tree[rson].size+1;
tree[x].sum[0][0]=tree[x].val[0];
if(lson)tree[x].sum[0][0]=tree[lson].sum[0][0]*tree[x].sum[0][0];
if(rson)tree[x].sum[0][0]=tree[x].sum[0][0]*tree[rson].sum[0][0];
tree[x].sum[1][0]=tree[x].val[1];
if(lson)tree[x].sum[1][0]=tree[lson].sum[1][0]*tree[x].sum[1][0];
if(rson)tree[x].sum[1][0]=tree[x].sum[1][0]*tree[rson].sum[1][0];
tree[x].sum[0][1]=tree[x].val[0];
if(rson)tree[x].sum[0][1]=tree[rson].sum[0][1]*tree[x].sum[0][1];
if(lson)tree[x].sum[0][1]=tree[x].sum[0][1]*tree[lson].sum[0][1];
tree[x].sum[1][1]=tree[x].val[1];
if(rson)tree[x].sum[1][1]=tree[rson].sum[1][1]*tree[x].sum[1][1];
if(lson)tree[x].sum[1][1]=tree[x].sum[1][1]*tree[lson].sum[1][1];
}
void pushfl(int x){
swap(tree[x].sum[0][0],tree[x].sum[1][0]);
swap(tree[x].sum[0][1],tree[x].sum[1][1]);
swap(tree[x].val[0],tree[x].val[1]);
tree[x].fl^=1;
}
void pushrev(int x){
swap(tree[x].sum[0][0],tree[x].sum[0][1]);
swap(tree[x].sum[1][0],tree[x].sum[1][1]);
swap(lson,rson);
tree[x].rev^=1;
}
void pushdown(int x){
if(tree[x].fl){
if(lson)pushfl(lson);
if(rson)pushfl(rson);
tree[x].fl=false;
}
if(tree[x].rev){
if(lson)pushrev(lson);
if(rson)pushrev(rson);
tree[x].rev=false;
}
}
void split(int now,int k,int &x,int &y){
if(!now){
x=y=0;return;
}
pushdown(now);
if(tree[tree[now].son[0]].size+1<=k){
x=now;split(tree[now].son[1],k-tree[tree[now].son[0]].size-1,tree[x].son[1],y);
pushup(x);
}
else{
y=now;split(tree[now].son[0],k,x,tree[y].son[0]);
pushup(y);
}
}
int merge(int x,int y){
if(!x||!y)return x|y;
pushdown(x);pushdown(y);
if(tree[x].data<tree[y].data){
tree[x].son[1]=merge(tree[x].son[1],y);
pushup(x);return x;
}
else{
tree[y].son[0]=merge(x,tree[y].son[0]);
pushup(y);return y;
}
}
void ins(int x){
rt=merge(rt,New(x));
}
void fl(int l,int r){
split(rt,l-1,x,y);
split(y,r-l+1,y,z);
pushfl(y);
rt=merge(merge(x,y),z);
}
void rev(int l,int r){
split(rt,l-1,x,y);
split(y,r-l+1,y,z);
pushrev(y);
rt=merge(merge(x,y),z);
}
void query(){
Mat ans=P*tree[rt].sum[0][0];
printf("%d %d\n",ans.a[0][0],ans.a[1][0]);
}
int main(){
scanf("%d%d%s",&n,&q,s+1);
W.a[0][0]=W.a[1][0]=W.a[1][1]=1;
E.a[0][0]=2;E.a[0][1]=1;E.a[1][0]=mod-1;
P.a[0][0]=P.a[1][0]=P.a[1][1]=1;
for(int i=1;i<=n;i++)ins(s[i]=='E');
query();
while(q--){
char od[10];scanf("%s",od);
if(od[0]=='A'){
scanf("%s",od);
ins(od[0]=='E');
}
else if(od[0]=='F'){
int l,r;scanf("%d%d",&l,&r);
fl(l,r);
}
else{
int l,r;scanf("%d%d",&l,&r);
rev(l,r);
}
query();
}
return 0;
}
[NOI2021] 机器人游戏
考虑容斥。对于钦定 \(k\) 个起点合法的情况,容斥系数为 \((-1)^{k+1}\)。
暴力枚举起点集合,然后对于每个位置从起点开始扫一遍,可以得到一些一个格子在经过之后变成 \(0/1/x/1-x\) 的限制。每个格子独立,那么分别看贡献:
- 同时有 \(0/1\) 或同时有 \(x/1-x\):空,一种。
- 前两个有一个,后两个有一个:两种。
- 只有一个:三种。
- 爆炸:只能全空。
暴力枚举复杂度 \(O(n^2m2^n)\)。听说改成 dfs 可以去掉一个 \(n\)。
数据范围 \(n\le 32\),考虑折半。枚举最后一个起点 \(p\),发现如果 \(p>\dfrac n2\) 那么走了超过 \(\dfrac n2\) 的全部爆炸。那么对于 \(p\le\dfrac n2\) 的,暴力枚举起点集合。对于剩下的考虑这样一个 dp:设 \(dp_{i,s,0/1}\) 为最后一个位置为 \(i\),前边 \(n-p\) 个是否选取状态为 \(s\),更前边有无被选的起点的方案数,转移考虑是否选当前格子:\(p\) 之前的可能选,\(p\) 必须选,后边的都不能选,然后计算当前串一个格子的贡献即可。
发现所有 \(m\) 个序列的 dp 都是一样的,所以可以用 bitset 压起来整体进行 dp,一次计算是 \(O(\frac m\omega)\) 的,于是总复杂度 \(O(\frac{nm2^{n/2}}\omega)\)。
//It's all about my own private universe, the end where it all begins and the trash recyle shop.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <bitset>
using namespace std;
const int mod=1000000007;
int n,m,k,ans,pw2[1010],pw3[1010],cnt[1<<16],dp[2][1<<16][2];
char s[110];
bitset<1010>now,vis[1010];
struct node{
bitset<1010>v[4];
node operator|(const node&s)const{
node ans;
for(int i=0;i<4;i++)ans.v[i]=v[i]|s.v[i];
return ans;
}
int calc(){
bitset<1010>a,b,c;
a=now&~((v[0]&v[1])|(v[2]&v[3]));
b=a&(v[0]^v[1]);c=a&(v[2]^v[3]);
int cnt1=b.count()+c.count(),cnt2=(b&c).count();
return 1ll*pw2[cnt2]*pw3[cnt1-2*cnt2]%mod;
}
}st[1010],tmp[1<<16];
int val[1<<16][2];
void solve(int id){
now=vis[id-1];
memset(dp,0,sizeof(dp));dp[0][0][0]=mod-1;
int cur=0;
for(int i=0;i<(1<<n-id+1);i++){
node ret=tmp[i];
val[i][0]=ret.calc();
ret.v[2].set();
val[i][1]=ret.calc();
}
const int U=(1<<n-id+1)-1;
for(int i=1;i<=n;i++){
cur^=1;
for(int s=0;s<(1<<n-id+1);s++)dp[cur][s][0]=dp[cur][s][1]=0;
for(int s=0;s<(1<<n-id+1);s++){
int t=U&(s<<1),od=s>>n-id;
if(i!=id){
if(i<id)dp[cur][t][od]=(dp[cur][t][od]+1ll*dp[cur^1][s][0]*val[t][1])%mod;
else dp[cur][t][od]=(dp[cur][t][od]+1ll*dp[cur^1][s][0]*val[t][od])%mod;
dp[cur][t][1]=(dp[cur][t][1]+1ll*dp[cur^1][s][1]*val[t][1])%mod;
}
if(i<=id){
t|=1;
if(i<id)dp[cur][t][od]=(dp[cur][t][od]-1ll*dp[cur^1][s][0]*val[t][1]%mod+mod)%mod;
else dp[cur][t][od]=(dp[cur][t][od]-1ll*dp[cur^1][s][0]*val[t][od]%mod+mod)%mod;
dp[cur][t][1]=(dp[cur][t][1]-1ll*dp[cur^1][s][1]*val[t][1]%mod+mod)%mod;
}
}
}
for(int i=0;i<(1<<n-id+1);i++)ans=(1ll*ans+dp[cur][i][0]+dp[cur][i][1])%mod;
}
int main(){
scanf("%d%d",&n,&m);k=n>>1;pw2[0]=pw3[0]=1;
for(int i=1;i<=m;i++)pw2[i]=2ll*pw2[i-1]%mod,pw3[i]=3ll*pw3[i-1]%mod;
for(int i=1;i<=m;i++)st[0].v[2][i]=1;
for(int i=1;i<=m;i++){
scanf("%s",s+1);
int len=strlen(s+1);
int p=2,pos=1;
for(int j=1;j<=len;j++){
if(s[j]=='R'){
st[pos].v[p][i]=1;
p=2;pos++;
}
else if(s[j]=='0')p=0;
else if(s[j]=='1')p=1;
else if(s[j]=='*')p^=1;
}
st[pos].v[p][i]=1;
for(int j=pos+1;j<=n+1;j++)st[j].v[2][i]=1;
for(int j=0;j<n;j++)if(pos+j<=n)vis[j][i]=1;
}
for(int i=1;i<(1<<k);i++)cnt[i]=__builtin_popcount(i)&1?mod-1:1;
for(int i=1;i<=n;i++){
for(int s=1;s<(1<<k);s++){
if(__lg(s&-s)>=i)tmp[s]=tmp[s&(s-1)]|st[0];
else tmp[s]=tmp[s&(s-1)]|st[i-__lg(s&-s)];
now=vis[__lg(s)];
cnt[s]=1ll*cnt[s]*tmp[s].calc()%mod;
}
}
for(int i=1;i<(1<<k);i++)ans=(ans-cnt[i]+mod)%mod;
for(int s=1;s<(1<<k+(n&1));s++)tmp[s]=tmp[s&(s-1)]|st[__lg(s&-s)+1];
for(int i=k+1;i<=n;i++)solve(i);
printf("%d\n",ans);
return 0;
}