2024 Noip 做题记录(一)
Round #1 - 20240909
A. [P10997] Color
题目大意
你有
行 列的一个矩阵,第 行第 列的格子(记作 )上写有一个整数 ,你可以把所有格子染上红、橙、黄、绿四种颜色之一。
- 红色格子的上方只能是红色格子,左边只能是红色或黄色格子,右边只能是红色或橙色格子。
- 橙色格子的右边只能是橙色格子,上方只能是橙色或红色格子,下方只能是橙色或绿色格子。
- 绿色格子的下方只能是绿色格子,右边只能是绿色或橙色格子,左边只能是绿色或黄色格子。
- 黄色格子的左边只能是黄色格子,下方只能是黄色或绿色格子,上方只能是黄色或红色格子。
四种颜色的权值分别为
,记 为 所染颜色的权值,最大化 。 数据范围:
。
思路分析
这个做法可以解决
容易注意到,“红格子加绿格子将网格分成左右两部分”或“黄格子加橙格子将网格分成上下两部分”至少有一个条件成立。
不妨设红格子加绿格子能将网格分成左右两部分,剩余的情况可以旋转网格解决,那么一定存在
设
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005;
const ll inf=4e18;
ll z[MAXN][MAXN],tmp[MAXN][MAXN],S[MAXN][MAXN];
ll a[MAXN][MAXN],b[MAXN][MAXN],c[MAXN][MAXN],d[MAXN][MAXN],tot[MAXN];
ll l[MAXN],r[MAXN],t[MAXN];
ll solve(int n,int m,int U,int D,int L,int R) {
memset(S,0,sizeof(S)),memset(tot,0,sizeof(tot));
ll ans=-inf;
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) S[i][j]=S[i][j-1]+z[i][j];
tot[i]=tot[i-1]+S[i][m];
}
memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
memset(c,0,sizeof(c)),memset(d,0,sizeof(d));
for(int i=n;i>=1;--i) {
ll w=0;
for(int j=1;j<=m;++j) w=max(w,c[i+1][j]),c[i][j]=w+(L-D)*S[i][j];
w=0;
for(int j=m;j>=1;--j) w=max(w,d[i+1][j]),d[i][j]=w+(R-D)*(S[i][m]-S[i][j-1]);
}
for(int i=1;i<=n;++i) {
ll w=0;
for(int j=1;j<=m;++j) w=max(w,a[i-1][j]),a[i][j]=w+(L-U)*S[i][j];
w=0;
for(int j=m;j>=1;--j) w=max(w,b[i-1][j]),b[i][j]=w+(R-U)*(S[i][m]-S[i][j-1]);
memset(l,0,sizeof(l)),memset(l,0,sizeof(l));
for(int j=1;j<=m;++j) l[j]=max(l[j-1],c[i+1][j]);
for(int j=m;j>=1;--j) r[j]=max(r[j+1],d[i+1][j]);
for(int j=0;j<=m;++j) {
ans=max(ans,a[i][j]+b[i][j+1]+l[j]+r[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
}
if(i==n) break;
memset(t,0,sizeof(t));
for(int j=m;j>=1;--j) t[j]=max(t[j+1],b[i][j]);
for(int j=1;j<m;++j) {
ans=max(ans,a[i][j]+d[i+1][j+1]+l[j]+t[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
}
memset(t,0,sizeof(t));
for(int j=1;j<=m;++j) t[j]=max(t[j-1],a[i][j]);
for(int j=1;j<m;++j) {
ans=max(ans,b[i][j+1]+c[i+1][j]+t[j]+r[j+1]+tot[i]*U+(tot[n]-tot[i])*D);
}
}
return ans;
}
signed main() {
ios::sync_with_stdio(false);
int n,m; cin>>n>>m;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) cin>>z[i][j],tmp[j][i]=z[i][j];
ll ans=solve(n,m,1,4,3,2);
swap(z,tmp);
ans=max(solve(m,n,3,2,1,4),ans);
cout<<ans<<"\n";
return 0;
}
B. [P11038] Decomposition
题目大意
给定
个点的有根带权树,对于 ,求出:每个节点可以选择 条出边,将其边权变成 ,此时最小的最大深度。 数据范围:
。
思路分析
容易想到简单贪心,每次把最深的
注意到如果一个点的度数
容易发现一个点只会出现
。
但是对于一个度数
用平衡树维护
时间复杂度
代码呈现
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define ll long long
using namespace std;
using namespace __gnu_pbds;
const int MAXN=2e5+5;
tree<array<ll,2>,null_type,greater<array<ll,2>>,
rb_tree_tag,tree_order_statistics_node_update> Q[MAXN];
struct Edge { int v,w; };
vector <Edge> G[MAXN],T[MAXN];
int n,deg[MAXN],dfn[MAXN],dcnt,st[MAXN][20],val[MAXN];
int dep[MAXN],up[MAXN][20];
void dfs0(int u,int fz) {
dep[u]=dep[fz]+1,dfn[u]=++dcnt,st[dcnt][0]=up[u][0]=fz;
for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
for(auto e:G[u]) if(e.v^fz) {
Q[u].insert({e.w,e.v}),val[e.v]=e.w,dfs0(e.v,u),++deg[u];
}
}
int gs(int x,int r) {
for(int k=19;~k;--k) if(dep[up[x][k]]>dep[r]) x=up[x][k];
return x;
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int LCA(int x,int y) {
if(x==y) return x;
int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
return cmp(st[l][k],st[r-bit(k)+1][k]);
}
ll f[MAXN];
void dfs1(int u,int k) {
f[u]=0;
for(auto e:T[u]) dfs1(e.v,k),f[u]=max(f[u],f[e.v]);
if(deg[u]<=k) return ;
for(auto e:T[u]) Q[u].erase({val[e.w],e.w}),Q[u].insert({f[e.v]+val[e.w],e.w});
f[u]=max(f[u],(*Q[u].find_by_order(k))[0]);
for(auto e:T[u]) Q[u].erase({f[e.v]+val[e.w],e.w}),Q[u].insert({val[e.w],e.w});
}
void solve(vector<int>&id,int k) {
sort(id.begin(),id.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
for(int i=1,m=id.size();i<m;++i) id.push_back(LCA(id[i-1],id[i]));
sort(id.begin(),id.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
id.erase(unique(id.begin(),id.end()),id.end());
for(int i=1,m=id.size();i<m;++i) {
int x=LCA(id[i-1],id[i]);
T[x].push_back({id[i],gs(id[i],x)});
}
dfs1(1,k);
for(int u:id) T[u].clear();
}
vector <int> id[MAXN];
signed main() {
scanf("%d",&n);
for(int i=1,u,v,w;i<n;++i) {
scanf("%d%d%d",&u,&v,&w);
G[u].push_back({v,w}),G[v].push_back({u,w});
}
dfs0(1,0);
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
}
for(int i=0;i<n;++i) id[i].push_back(1);
for(int u=2;u<=n;++u) for(int i=0;i<deg[u];++i) id[i].push_back(u);
for(int i=0;i<n;++i) solve(id[i],i),printf("%lld ",f[1]);
puts("");
return 0;
}
C. [P10039] Tree
题目大意
给定
个点的有根树,和一个点集 ,求有多少有根树满足 中任意两点的 标号都相同(两棵树根可以不同)。 数据范围:
。
思路分析
容易发现题目中的限制等价于
先假定树根等于虚树的根。
求出虚树大小
注意到答案只和
如果树根不是虚树的根,那么相当于将新的根变成虚树根的父亲,即
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) {
ll ret=1;
for(;b;a=a*a%MOD,b>>=1) if(b&1) ret=ret*a%MOD;
return ret;
}
ll fac[MAXN],ifac[MAXN],pw[MAXN];
ll C(int x,int y) {
if(x<0||y<0||y>x) return 0;
return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
ll calc(int n,int m) {
ll ans=0;
for(int i=0;i<=n-m;++i) {
ans=(ans+fac[m+i-2]*ifac[m-2]%MOD*C(n-m,i)%MOD*(i<n-m?((m+i)*pw[n-m-i-1]%MOD):1))%MOD;
}
return ans;
}
vector <int> G[MAXN];
int n,m,cnt;
bool f[MAXN];
void dfs(int u) {
bool ok=f[u];
for(int v:G[u]) dfs(v),ok|=(f[u]&&f[v]),f[u]|=f[v];
cnt+=ok;
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=fac[0]=pw[0]=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD,pw[i]=pw[i-1]*n%MOD;
ifac[n]=ksm(fac[n]);
for(int i=n;i;--i) ifac[i-1]=ifac[i]*i%MOD;
for(int i=2,u;i<=n;++i) scanf("%d",&u),G[u].push_back(i);
for(int i=1,x;i<=m;++i) scanf("%d",&x),f[x]=true;
dfs(1);
ll ans=calc(n,cnt);
if(cnt<n) ans=(ans+(n-cnt)*calc(n,cnt+1))%MOD;
printf("%lld\n",ans);
return 0;
}
D. [P10926] Operation
题目大意
给定
,定义 。 对于一个集合
,定义 , 。 给定一个长度为
,由 组成的操作序列。 维护一个集合序列
,初始 ,支持如下 次操作。
- 向
的末尾中加入某个 顺次经过操作序列中 元素后的结果。 - 查询是否有
。 - 对某个
,求他在生成过程中包含了几次 。 数据范围:
。
思路分析
观察发现
注意到
因为此时
手玩发现对于所有
进一步手玩会发现
因此我们可以用线段树快速维护某个
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef array<int,8> arr;
const int MAXN=1<<18;
int n,m,q,siz[8];
bool a[4][MAXN];
//0:A, 1:~A, 2:D(A), 3:~D(A), 4:U, 5:0, 6:1, 7:~1
struct LB {
int t[20],z;
LB() { memset(t,0,sizeof(t)),z=0; }
void ins(int x) {
for(int k=n-1;~k;--k) if(x>>k&1) {
if(!t[k]) return ++z,t[k]=x,void();
x^=t[k];
}
}
void dfs(bool *f) {
int p=0;
for(int i=0;i<n;++i) if(t[i]) p|=1<<i;
for(int s=0;s<(1<<n);++s) if((s&p)==s) {
int x=0;
for(int i=0;i<n;++i) if(s>>i&1) x^=t[i];
f[x]=true;
}
}
};
arr B={1,0,3,2,5,4,7,6},D={4,4,2,4,4,6,6,4};
char op[MAXN];
int val[MAXN];
arr cnt[MAXN];
struct info {
arr p,f[8]; //start from x, cnt #y
info() { p.fill(0); for(int i=0;i<8;++i) f[i].fill(0); }
friend info operator +(const info &L,const info &R) {
info z;
for(int x=0;x<8;++x) {
z.f[x]=L.f[x],z.p[x]=R.p[L.p[x]];
for(int y=0;y<8;++y) z.f[x][y]+=R.f[L.p[x]][y];
}
return z;
}
void add(int &x,arr &g) {
for(int y=0;y<8;++y) g[y]+=f[x][y];
x=p[x];
}
};
struct SegmentTree {
info tr[MAXN<<1];
void init(int l=1,int r=m,int p=1) {
if(l==r) {
tr[p].p=(op[l]=='D'?D:B);
for(int x=0;x<8;++x) ++tr[p].f[x][tr[p].p[x]];
return ;
}
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
void qry(int ul,int ur,int&x,arr&g,int l=1,int r=m,int p=1) {
if(ul<=l&&r<=ur) return tr[p].add(x,g);
int mid=(l+r)>>1;
if(ul<=mid) qry(ul,ur,x,g,l,mid,p<<1);
if(mid<ur) qry(ul,ur,x,g,mid+1,r,p<<1|1);
}
} TR;
int Q(int c,int x) { return a[c>>1][x]^(c&1); }
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m>>q;
for(int i=0;i<(1<<n);++i) cin>>a[0][i];
if(n==1) D[0]=0,D[1]=1;
else {
LB S,T;
for(int i=0;i<(1<<n);++i) (a[0][i]?S:T).ins(i);
if(S.z<n) D[0]=2,S.dfs(a[1]);
if(T.z<n) D[1]=2,T.dfs(a[1]);
}
for(int i=0;i<(1<<n);++i) a[2][i]=1;
a[3][0]=1;
for(int c:{0,1,2,3}) {
int x=0;
for(int i=0;i<(1<<n);++i) x+=a[c][i];
siz[c<<1]=x,siz[c<<1|1]=(1<<n)-x;
}
for(int i=1;i<=m;++i) cin>>op[i];
TR.init(),val[0]=0;
for(int z=0,w,v,k,l,r;q--;) {
cin>>w>>v;
if(w==1) {
cin>>l>>r,++z,val[z]=val[v],cnt[z].fill(0);
TR.qry(l,r,val[z],cnt[z]);
cout<<siz[val[z]]<<"\n";
} else {
cin>>k;
if(w==2) cout<<Q(val[v],k)<<"\n";
else {
int s=0;
for(int c=0;c<8;++c) if(Q(c,k)) s+=cnt[v][c];
cout<<s<<"\n";
}
}
}
return 0;
}
Round #2 - 20240910
A. [P10891] Set
题目大意
给定
阶排列 ,定义 表示 的前缀最大值对应的下标集合。
次询问给定 ,求 。 数据范围:
。
思路分析
显然
考虑这个东西怎么刻画,不妨设
但这个刻画方式依然关系
又因为对于所有
这样我们就成功地将问原式转成和
设
那么我们只要预处理
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e7+5,MOD=998244353;
int n,X,C,q,a[MAXN];
int stk[MAXN],tp,pre[MAXN],s[MAXN];
int fl[MAXN],fr[MAXN];
ll W(ll x) { return x*(x+1)>>1; }
signed main() {
scanf("%d%d%d%d",&n,&X,&C,&q);
iota(a+1,a+n+1,1);
for(int i=1,l,r;i<=C;++i) {
l=(1ll*X*(X^i))%n+1;
r=(X^(1ll*i*i))%n+1;
swap(a[l],a[r]);
}
for(int i=n;i>=1;--i) {
while(tp&&a[stk[tp]]<a[i]) pre[stk[tp--]]=i+1;
stk[++tp]=i,s[i]=tp;
}
for(int i=1;i<=tp;++i) pre[stk[i]]=1;
stk[tp=0]=0;
for(int i=1,sum=0;i<n;++i) {
while(tp&&s[stk[tp]]>s[i]) {
sum=(sum-1ll*(s[stk[tp]]-1)*(stk[tp]-stk[tp-1]))%MOD,--tp;
}
sum=(sum+1ll*(s[i]-1)*(i-stk[tp]))%MOD,stk[++tp]=i;
if(sum<0) sum+=MOD;
fl[i+1]=(fl[i]+sum)%MOD;
}
stk[tp=0]=n;
for(int i=n-1,sum=0;i>=1;--i) {
while(tp&&s[stk[tp]]>s[i]) {
sum=(sum-1ll*(s[stk[tp]]-1)*(stk[tp-1]-stk[tp]))%MOD,--tp;
}
sum=(sum+1ll*(s[i]-1)*(stk[tp]-i))%MOD,stk[++tp]=i;
if(sum<0) sum+=MOD;
fr[i]=(fr[i+1]+sum)%MOD;
}
for(int i=1;i<=n;++i) s[i]=(s[i]+s[i-1])%MOD;
int res=0;
for(int o=1,l,r;o<=q;++o) {
l=(1ll*X*o+(X^(1ll*X*o)))%n+1;
r=(X-o+(X^(X+o)))%n+1;
if(l>r) swap(l,r);
int ans=(1ll*(r-l+1)*(s[r]-s[l-1])%MOD+fl[n]-fl[r]-fr[l])%MOD;
ans=(ans-1ll*(l-1)*(s[n]-s[r])%MOD-1ll*(n-r)*s[l-1]%MOD)%MOD;
if(ans<0) ans+=MOD;
res^=ans;
}
printf("%d\n",res);
return 0;
}
B. [P10872] String
题目大意
对于两个长度为
的 01 串 ,定义 表示所有 的循环同构串中,公共元素最多的一对。 给定
中 的个数 ,构造 使 最小。 数据范围:
。
思路分析
考虑
容易发现偏移量为
那么我们就要求
很显然
-
取
, ,那么 对 的贡献就是 区间 。容易证明这就是最平均的构造,如果
,那么接着取 即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
signed main() {
ios::sync_with_stdio(false);
int n,x,y;
cin>>n>>x>>y;
string s(n,'0'),t(n,'0');
cout<<(1ll*x*y+n-1)/n<<"\n";
for(int i=0;i<x;++i) s[i]='1';
int z=__gcd(n,x);
for(int i=0;y;++i) {
int p=i;
for(int k=0;k<n/z;++k) {
++t[(n-p)%n],p=(p+x)%n;
if(!--y) break;
}
}
cout<<s<<"\n"<<t<<"\n";
return 0;
}
C. [P10873] Guess
题目大意
给
个人,每个人的帽子为黑色或白色,每个人可以看到其他人的帽子颜色。 你要为每个人确定一组猜自己头上帽子颜色的策略,使得任何情况下,如果当前有
个戴黑帽子的人,那么其中猜对自己帽子颜色的人不少于 个,戴白帽子且猜对的人不少于 个。 数据范围:
。
思路分析
从一个简化的问题的开始,如果想让猜对自己帽子颜色的人总数不少于
考虑帽子为黑色的人的集合
因此我们可以将
那么对于每种状态,猜对的人等于其出度,要求就是每个点出度至少为入度
这是经典欧拉回路模型,把所有度数为奇数的点向一个虚点连边,求出欧拉回路后每个点出度与入度之差
但这题要对黑帽子的人和白脑子的人分别限制,那么拆点表示两个限制,连边
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5,MAXM=1e7+5;
struct Edge { int v,id,x; };
vector <Edge> G[MAXN];
bool vis[MAXM];
string S[20];
int n,m,cur[MAXN];
int ns(int s,int x) {
int t=0;
for(int i=0;i<n;++i) if(i^x) t=t<<1|(s>>i&1);
return t;
}
void dfs(int u) {
for(int &i=cur[u];i<(int)G[u].size();++i) {
Edge e=G[u][i];
if(vis[e.id]) continue;
vis[e.id]=true,dfs(e.v);
if(~e.x) S[e.x][ns(u,e.x)]="BC"[u>>e.x&1];
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;++i) S[i]=string(1<<(n-1),'.');
int U=(1<<n);
for(int s=0;s<U;++s) for(int i=0;i<n;++i) if(s>>i&1) {
G[s|U].push_back({s^(1<<i),m,i}),G[s^(1<<i)].push_back({s|U,m,i}),++m;
}
for(int s=0;s<2*U;++s) if(G[s].size()&1) {
G[s].push_back({2*U,m,-1}),G[1<<n].push_back({2*U,m,-1}),++m;
}
for(int i=0;i<=2*U;++i) dfs(i);
for(int i=0;i<n;++i) cout<<S[i]<<"\n";
return 0;
}
D. [P8421] Interval
题目大意
给定
,记 。
次询问 内所有子区间的权值和。 数据范围:
。
思路分析
考虑
因此会对
对
为了快速求答案,我们维护
对于
对于
因此容易把
求
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ui unsigned int
using namespace std;
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x;
}
void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
void write(ui x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
if(ow>=olim) flush();
}
void putc(char c) {
obuf[ow++]=c;
if(ow>=olim) flush();
}
#undef gc
}
namespace gcd {
const int MAXN=1e6+5,B=1005;
vector <int> pr;
array<int,3> a[MAXN];
bool isc[MAXN];
int val[B][B];
void init() {
a[1]={1,1,1};
for(int i=2;i<MAXN;++i) {
if(!isc[i]) pr.push_back(i),a[i]={1,1,i};
for(int p:pr) {
if(i*p>=MAXN) break;
isc[i*p]=true,a[i*p]=a[i],a[i*p][0]*=p;
sort(a[i*p].begin(),a[i*p].end());
if(i%p==0) break;
}
}
for(int i=0;i<B;++i) val[i][0]=val[0][i]=i;
for(int i=1;i<B;++i) for(int j=1;j<=i;++j) val[j][i]=val[i][j]=val[j][i%j];
}
int q(int x,int y) {
int ans=1;
for(int k:a[x]) {
int w=(k<B?val[k][y%k]:(y%k?1:k));
ans*=w,y/=w;
}
return ans;
}
}
using IO::read;
const int MAXN=1e6+5,MAXQ=5e6+5;
ui x[MAXN],y[MAXN];
int a[MAXN],b[MAXN],c[MAXN];
int hd[MAXN],L[MAXQ],lst[MAXQ],ans[MAXQ];
signed main() {
int n=read(),q=read(); gcd::init();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
for(int i=1;i<=n;++i) c[i]=read();
for(int i=1,r;i<=q;++i) L[i]=read(),r=read(),lst[i]=hd[r],hd[r]=i;
for(int i=1;i<=n;++i) {
int k=i-1;
for(;k>0;--k) {
int A=a[k]&a[k+1],B=b[k]|b[k+1],C=gcd::q(c[k],c[k+1]);
if(A==a[k]&&B==b[k]&&C==c[k]) break;
a[k]=A,b[k]=B,c[k]=C;
}
x[i]=x[i-1],y[i]=y[i-1];
for(int j=k+1;j<=i;++j) {
ui val=x[j]*(i-1)+y[j];
x[j]=x[j-1]+(ui)a[j]*b[j]*c[j];
y[j]=val-x[j]*(i-1);
}
ui V=x[i]*i+y[i];
for(int j=hd[i];j;j=lst[j]) ans[j]=V-(x[L[j]-1]*i+y[L[j]-1]);
}
for(int i=1;i<=q;++i) IO::write(ans[i]),IO::putc('\n');
IO::flush();
return 0;
}
*E. [P10896] Bracket
题目大意
定义一个括号串
的权值为其最大括号匹配的大小。 对于
个括号串 ,定义其权值为:任意重排 后连接成完整括号串 , 最小的权值。 已知
的长度,在 的每个字符都均匀随机生成的情况下,求每组 的期望。 数据范围:
。
思路分析
考虑如何刻画一个括号串
求出起点到最低点的距离
然后考虑刻画
那么对于其他的每个串
那么一组
注意到
考虑拆贡献,先处理前一部分的贡献,我们可以对于每个
因此我们就将原问题转成求
反面考虑,等价于对每个
因此我们得到:
考虑如何解决单个
这就相当于求有多少条折线长度为
考虑
- 只有起点高度
且最后一步终点从 的路径会从不合法变合法。 - 只有起点高度
且最后一步终点从 的路径会从不合法变合法。
我们设
初始概率为
对
带入得到:
注意到
考虑用和式表示,记
边界条件是
注意到
很显然
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e6+5,MOD=1e9+7,inv=(MOD+1)/2;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll fac[MAXN],ifac[MAXN],ipw[MAXN],p[MAXN];
ll C(int x,int y) {
if(x<0||y<0||y>x) return 0;
return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,m,a[MAXN];
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),m+=a[i];
for(int i=fac[0]=ipw[0]=1;i<=m;++i) fac[i]=fac[i-1]*i%MOD,ipw[i]=ipw[i-1]*inv%MOD;
ifac[m]=ksm(fac[m]);
for(int i=m;i;--i) ifac[i-1]=ifac[i]*i%MOD;
for(int i=0;i<=m;++i) p[i]=1;
for(int o=1;o<=n;++o) {
int x=a[o]/2; ll w=0;
for(int i=0;i<=x;++i) w=(w+C(2*x+1,i+x+1)*ipw[2*x])%MOD,p[i]=p[i]*w%MOD;
}
ll ans=0,sum=0;
for(int i=0;i<m;++i) ans=(ans+1+MOD-p[i])%MOD;
for(int o=1;o<=n;++o) {
int x=a[o];
for(int i=0;i<=x;++i) sum=(sum+ipw[x]*C(x,i)%MOD*abs(2*i-x))%MOD;
}
printf("%lld\n",(m+MOD-(sum+2*ans)%MOD)*inv%MOD);
return 0;
}
Round #3 - 20240912
A. [P10163] Square
题目大意
给定
个点 条边的有向图,满足边看成无向边后图是森林,增加若干点和有向边,使得每个点出度都是完全平方数,且边看成无向边后图是一棵树。 数据范围:
。
思路分析
对每个点,计算出至少加多少出边后出度变成平方数,算出需要加的边数量总和
如果
具体构造的时候可以取出所有每个点都合法的连通块,每个点都连向其中的一个连通块,剩余的连通块每个点匹配一个即可。
如果
注意到每个连通块中一定有出度为
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1.1e5+5,B=317;
int w[MAXN],d[MAXN],dsu[MAXN],id[MAXN];
bool typ[MAXN];
vector <int> P[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
signed main() {
for(int i=0,j=0;i<=B;++i) while(j<=i*i) w[j++]=i*i;
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i) dsu[i]=i;
for(int i=1,u,v;i<=m;++i) cin>>u>>v,++d[u],dsu[find(u)]=find(v);
int c=0,k=0;
for(int i=1;i<=n;++i) c+=(dsu[i]==i),k+=w[d[i]]-d[i];
if(!k&&c==1) return cout<<"0 0\n",0;
if(k>=c) {
cout<<k-c+1<<" "<<k<<"\n";
for(int i=1;i<=n;++i) P[find(i)].push_back(i),typ[find(i)]|=(w[d[i]]>d[i]);
vector <int> a,b;
for(int i=1;i<=n;++i) if(dsu[i]==i) (typ[i]?a:b).push_back(i);
for(int i=1;i<=k-c+1;++i) b.push_back(n+i);
int o=1;
for(int x:a) {
bool f=0;
for(int u:P[x]) if(w[d[u]]>d[u]) {
int r=w[d[u]]-d[u];
if(!f) cout<<u<<" "<<b[0]<<"\n",--r,f=1;
while(r--) cout<<u<<" "<<b[o++]<<"\n";
}
}
} else {
cout<<"0 "<<c-1<<"\n";
for(int i=1;i<=n;++i) P[find(i)].push_back(i),typ[find(i)]|=(w[d[i]]>d[i]);
vector <int> a,b;
for(int i=1;i<=n;++i) if(dsu[i]==i) (typ[i]?a:b).push_back(i);
if(b.empty()) b.push_back(a.back()),a.pop_back();
int o=1;
for(int x:a) {
bool f=0;
for(int u:P[x]) if(w[d[u]]>d[u]) {
int r=w[d[u]]-d[u];
if(!f) cout<<u<<" "<<b[0]<<"\n",--r,f=1,++d[u],dsu[find(u)]=find(b[0]);
while(r--) cout<<u<<" "<<b[o]<<"\n",++d[u],dsu[find(u)]=find(b[o++]);
}
}
for(int i=1;i<=n;++i) if(!d[i]) id[find(i)]=i;
vector <int> z;
for(int i=1;i<=n;++i) if(dsu[i]==i) z.push_back(id[i]);
for(int i=1;i<(int)z.size();++i) cout<<z[i-1]<<" "<<z[i]<<"\n";
}
return 0;
}
B. [P10598] Bigraph
题目大意
给定左右各
个点 条边的二分图,求最大的完全二分子图,存在多个求左部点数最多的一个。 求在这张子图选出
条边后,每个点至少有一条出边被选的方案数。 数据范围:
。
思路分析
第一问等价于反图最大独立集,最大化左部点数可以将左部点点权设为
第二问只关心前一问求出的左右部点集大小
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
namespace F {
const int MAXV=205,MAXE=2e5+5;
struct Edge {
int v,f,lst;
} G[MAXE];
int S,T,tot=1,hd[MAXV],cur[MAXV],dep[MAXV];
void init() { tot=1,memset(hd,0,sizeof(hd)); }
void adde(int u,int v,int w) { G[++tot]={v,w,hd[u]},hd[u]=tot; }
void link(int u,int v,int w) { adde(u,v,w),adde(v,u,0); }
bool BFS() {
memcpy(cur,hd,sizeof(cur)),memset(dep,-1,sizeof(dep));
queue <int> Q;
Q.push(S),dep[S]=0;
while(!Q.empty()) {
int u=Q.front(); Q.pop();
for(int i=hd[u];i;i=G[i].lst) if(G[i].f&&dep[G[i].v]==-1) {
dep[G[i].v]=dep[u]+1,Q.push(G[i].v);
}
}
return ~dep[T];
}
int dfs(int u,int f) {
if(u==T) return f;
int r=f;
for(int i=cur[u];i;i=G[i].lst) {
int v=G[cur[u]=i].v;
if(G[i].f&&dep[v]==dep[u]+1) {
int g=dfs(v,min(r,G[i].f));
if(!g) dep[v]=-1;
G[i].f-=g,G[i^1].f+=g,r-=g;
}
if(!r) return f;
}
return f-r;
}
int Dinic() {
int f=0;
while(BFS()) f+=dfs(S,inf);
return f;
}
}
using F::link;
bool g[55][55];
const int MOD=19921228;
int C[2505][2505];
signed main() {
int n,m,k;
scanf("%d%d%d",&n,&k,&m);
for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),g[u][v]=true;
int s=F::S=2*n+1,t=F::T=2*n+2;
for(int i=1;i<=n;++i) link(s,i,101),link(i+n,t,100);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(!g[i][j]) link(i,j+n,inf);
int ans=201*n-F::Dinic();
int x=ans%100,y=ans/100-x;
printf("%d %d\n",x,y);
int tot=x*y;
for(int i=0;i<=tot;++i) for(int j=C[i][0]=1;j<=i;++j) {
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
int cnt=0;
for(int i=0;i<=x;++i) for(int j=0;j<=y;++j) if((x-i)*(y-j)>=k) {
int w=1ll*C[(x-i)*(y-j)][k]*C[x][i]%MOD*C[y][j]%MOD;
if((i+j)&1) cnt=(cnt+MOD-w)%MOD;
else cnt=(cnt+w)%MOD;
}
printf("%d\n",cnt);
return 0;
}
C. [P10646] Sentence
题目大意
给定字符串序列
,对于一个字符串序列 ,其权值定义为其所有长度为 的子区间 在 中出现次数的最小值。
次询问给定 ,构造 最大化 的权值。 数据范围:
。
思路分析
显然可以把
那么我们设每条边的边权就是这个子区间的出现次数,询问等价于求从某个点出发走
注意到边权总和为
快速回答询问只需要在反图上拓扑排序,求出每个点的最长路,仅考虑权值
对每个起点上的询问按
拓扑排序时记录最优转移即可构造方案。
注意我们需要在考虑权值
时间复杂度 map
维护的开销。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5,inf=1e9;
struct Edge { int u,v,c; };
struct Graph {
int deg[MAXN],f[MAXN],nxt[MAXN],col[MAXN];
vector <Edge> G[MAXN];
void build(const vector<int>&V,const vector<Edge>&E) {
if(V.empty()||E.empty()) return ;
for(int i:V) deg[i]=f[i]=nxt[i]=col[i]=0,G[i].clear();
for(auto e:E) ++deg[e.u],G[e.v].push_back({e.v,e.u,e.c});
queue <int> q;
for(int i:V) if(!deg[i]) q.push(i);
while(q.size()) {
int u=q.front(); q.pop();
for(auto e:G[u]) {
if(f[u]+1>f[e.v]) f[e.v]=f[u]+1,col[e.v]=e.c,nxt[e.v]=u;
if(!--deg[e.v]) q.push(e.v);
}
}
for(auto e:E) if(deg[e.u]&°[e.v]) nxt[e.u]=e.v,col[e.u]=e.c;
}
int qry(int x) { return deg[x]?inf:f[x]; }
void dfs(int u,int k,vector<int>&x) {
while(k--) x.push_back(col[u]),u=nxt[u];
}
} G[2];
int a[MAXN],b[MAXN],c[MAXN];
map <string,int> sid;
string wrd[MAXN],str;
map <vector<int>,int> qid;
map <array<int,2>,array<int,2>> edg;
vector <Edge> E[MAXN];
vector <int> V[MAXN];
vector <array<int,2>> Q[MAXN];
vector <int> ans[MAXN];
signed main() {
ios::sync_with_stdio(false);
int n,k,q,m=0;
cin>>n>>k;
for(int i=1,tot=0;i<=n;++i) {
cin>>str;
if(!sid.count(str)) sid[str]=++tot,wrd[tot]=str;
a[i]=sid[str];
}
for(int i=1;i+k-1<=n;++i) {
vector <int> t(a+i,a+i+k);
if(!qid.count(t)) qid[t]=++m;
b[i]=qid[t];
}
for(int i=1;i+k<=n;++i) {
int u=b[i],v=b[i+1];
if(!edg.count({u,v})) edg[{u,v}]={1,a[i+k]};
else ++edg[{u,v}][0];
}
for(auto e:edg) {
int u=e.first[0],v=e.first[1],w=e.second[0],h=e.second[1];
c[u]=max(c[u],w),c[v]=max(c[v],w);
for(int i=1;i<=w;++i) E[i].push_back({u,v,h});
}
for(int i=1;i<=m;++i) for(int j=1;j<=c[i];++j) V[j].push_back(i);
cin>>q;
for(int o=1,e;o<=q;++o) {
cin>>e;
vector <int> p;
bool flg=1;
for(int i=0;i<k;++i) {
cin>>str;
if(!sid.count(str)) p.push_back(-1),flg=0;
else p.push_back(sid[str]);
}
if(!flg||!qid.count(p)) ans[o].resize(e,1);
else Q[qid[p]].push_back({e,o});
}
for(int i=1;i<=m;++i) sort(Q[i].begin(),Q[i].end());
for(int o=1;o<=n-k;++o) {
G[o&1].build(V[o],E[o]);
for(int i:V[o]) {
while(Q[i].size()&&Q[i].back()[0]>G[o&1].qry(i)) {
auto z=Q[i].back();
if(o==1) ans[z[1]].resize(z[0],1);
else G[(o&1)^1].dfs(i,z[0],ans[z[1]]);
Q[i].pop_back();
}
if(c[i]==o) {
while(Q[i].size()) {
auto z=Q[i].back();
G[o&1].dfs(i,z[0],ans[z[1]]);
Q[i].pop_back();
}
}
}
}
for(int i=1;i<=q;++i) {
for(int x:ans[i]) cout<<wrd[x]<<" ";
cout<<"\n";
}
return 0;
}
*D. [P9293] Addition
题目大意
给定二进制数
,求 满足 且 二进制表示两两无交,最小化 。 数据范围:
, 表示 的二进制表示的位数。。
思路分析
先考虑如何判断一个
- 如果
那么用 直接消掉 。 - 如果
那么得到 。 - 如果
无解。
不断维护这个过程即可完成判定。
观察最高位
又观察到
因此
先判定
对于
注意我们在递归时是有最高位限制的,即钦定
注意到当前的每个 set
维护
我们可以证明这个过程的复杂度是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,inf=1e9;
int m=0;
struct SegmentTree {
array<int,2> tr[MAXN<<2]; int tg[MAXN<<2];
void adt(int p,int k) { tr[p][0]+=k,tg[p]+=k; }
void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void init(int l=1,int r=m,int p=1) {
tr[p]={-inf,r};
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
}
void add(int ul,int ur,int k,int l=1,int r=m,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
tr[p]=max(tr[p<<1],tr[p<<1|1]);
}
} T;
string s[MAXN];
vector <int> rk[MAXN],idx[MAXN];
int n,d[MAXN],a[MAXN],b[MAXN],pre[MAXN];
bool ans[MAXN<<1];
set <int> S;
void upd(int x,int op) {
if(~op) S.insert(x);
else S.erase(x);
T.add(x,x,op*(inf+d[x]));
if(x>1) T.add(1,x-1,op);
}
bool dfs(int lim) {
int mx=T.tr[1][0],x=T.tr[1][1];
if(mx<0) return true;
if(mx>=lim) return false;
upd(x,-1);
if(b[x]) upd(rk[a[x]][b[x]-1],1);
int c=0; vector <int> del;
while(S.size()&&*S.rbegin()>x) del.push_back(*S.rbegin()),upd(*S.rbegin(),-1),++c;
for(int i=0;i<=c;++i) ans[mx-i]=true;
if(dfs(mx-c)) return true;
ans[mx-c]=false;
if(b[x]) upd(rk[a[x]][b[x]-1],-1);
if(mx+1<lim) {
ans[mx+1]=true;
return dfs(mx-c+1);
} else {
for(int i=0;i<=c;++i) ans[mx-i]=false;
upd(x,1);
for(int i:del) upd(i,1);
return false;
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n; int k=0;
for(int i=1;i<=n;++i) {
cin>>s[i],k=max(k,(int)s[i].size());
reverse(s[i].begin(),s[i].end());
for(int j=0;j<(int)s[i].size();++j) if(s[i][j]=='1') idx[j].push_back(i);
}
for(int i=0;i<k;++i) {
sort(idx[i].begin(),idx[i].end(),[&](int x,int y){ return pre[x]<pre[y]; });
for(int x:idx[i]) {
pre[x]=++m,a[m]=x,d[m]=i;
b[m]=rk[x].size(),rk[x].push_back(m);
}
}
T.init();
for(int i=1;i<=n;++i) upd(rk[i].back(),1);
dfs(inf);
for(int i=n+k+1;~i;--i) if(ans[i]) {
for(int j=i;~j;--j) cout<<ans[j];
return cout<<"\n",0;
}
cout<<"0\n";
return 0;
}
*E. [P10016] Query
题目大意
给定
个点的树以及 ,初始 全为 ,支持如下操作:
- 给定
,将编号在 内的节点构成的斯坦纳树上每个点 的 加一,保证 随机。 - 给定
,求 ,其中 。 数据范围:
。
思路分析
注意到
考虑用 bitset
维护,我们用一个 bitset
维护当前所有 bitset
维护所有
先尝试用 bitset
维护所有
那么我们只要对于每个操作一,求出
第二部分是容易处理掉的,因此我们只要求
考虑分块,如果
对于每个关键点
因此对每个关键点算前后缀链并,复杂度
对于一个
取
那么我们只要对每个 bitset
,考虑如何刻画所有
考虑爆搜
暴力维护这个过程,精细实现后计算量
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=8e4+5,B=282,X=19901991,Y=20242024;
int n,q,z[MAXN],bel[MAXN],lp[MAXN],rp[MAXN];
vector <int> G[MAXN];
int fa[MAXN],dfn[MAXN],dcnt,st[MAXN][18],dl[MAXN][18],dr[MAXN][18];
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int qdl(int l,int r) {
int k=__lg(r-l+1);
return min(dl[l][k],dl[r-bit(k)+1][k]);
}
int qdr(int l,int r) {
int k=__lg(r-l+1);
return max(dr[l][k],dr[r-bit(k)+1][k]);
}
int lca(int l,int r) {
int k=__lg(r-l+1);
return cmp(st[l][k],st[r-bit(k)+1][k]);
}
void dfs0(int u,int fz) {
fa[u]=fz,dl[u][0]=dr[u][0]=dfn[u]=++dcnt,st[dcnt][0]=fz;
for(int v:G[u]) if(v^fz) dfs0(v,u);
}
int op[MAXN],ql[MAXN],qr[MAXN];
bitset <MAXN> F[MAXN],S;
vector <int> wl[MAXN],wr[MAXN],wx[MAXN],LCA[MAXN];
void add(bitset<MAXN>&s,int u) { for(;u&&!s[u];u=fa[u]) s.set(u); }
void dfs1(int u) {
S.set(u);
for(int i:LCA[u]) F[i]^=S;
for(int v:G[u]) if(v^fa[u]) dfs1(v);
S.reset(u);
}
int pr[MAXN],tot,g[MAXN];
bool isc[MAXN];
void dfs2(int o,int x,int y) {
for(int i:wx[x]) F[i]&=S;
for(int i=o;i<=tot&&1ll*x*pr[i]<=n;++i) {
int p=pr[i],w=(i==o?y*p:p);
for(int j=w;j<=n;j+=w) S[j]=z[g[j]*=p];
dfs2(i,x*pr[i],w);
for(int j=w;j<=n;j+=w) S[j]=z[g[j]/=p];
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>z[i],z[i]%=2;
for(int i=1;(i-1)*B+1<=n;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
fill(bel+lp[i],bel+rp[i]+1,i);
}
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs0(1,0);
for(int k=1;k<18;++k) for(int i=1;i+bit(k)-1<=n;++i) {
st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
dl[i][k]=min(dl[i][k-1],dl[i+bit(k-1)][k-1]);
dr[i][k]=max(dr[i][k-1],dr[i+bit(k-1)][k-1]);
}
for(int i=1,x;i<=q;++i) {
cin>>op[i]>>ql[i]>>qr[i];
if(op[i]==1) {
if(ql[i]==qr[i]) LCA[fa[ql[i]]].push_back(i);
else LCA[fa[lca(qdl(ql[i],qr[i])+1,qdr(ql[i],qr[i]))]].push_back(i);
if(bel[ql[i]]==bel[qr[i]]) for(int u=ql[i];u<=qr[i];++u) add(F[i],u);
else wl[bel[ql[i]]].push_back(i),wr[bel[ql[i]]+1].push_back(i);
} else cin>>x,wx[x].push_back(i);
}
for(int o=1;o<=bel[n];++o) {
sort(wl[o].begin(),wl[o].end(),[&](int x,int y){ return ql[x]>ql[y]; });
sort(wr[o].begin(),wr[o].end(),[&](int x,int y){ return qr[x]<qr[y]; });
S.reset();
for(int i=lp[o],j=0,s=wr[o].size();j<s;++i) {
add(S,i);
while(j<s&&qr[wr[o][j]]==i) F[wr[o][j++]]|=S;
}
S.reset();
for(int i=rp[o],j=0,s=wl[o].size();j<s;--i) {
add(S,i);
while(j<s&&ql[wl[o][j]]==i) F[wl[o][j++]]|=S;
}
}
S.reset(),dfs1(1);
for(int i=1;i<=q;++i) F[i]^=F[i-1];
for(int i=2;i<=n;++i) if(!isc[i]) {
pr[++tot]=i;
for(int j=2*i;j<=n;j+=i) isc[j]=true;
}
S.reset();
for(int i=1;i<=n;++i) S[i]=z[g[i]=1];
dfs2(1,1,1);
for(int i=1;i<=q;++i) if(op[i]==2) {
int l=ql[i],r=qr[i];
F[i]>>=l,F[i]<<=(MAXN-(r-l+1));
int s=F[i].count();
cout<<(1ll*s*(X-1)+r-l+1)%Y<<"\n";
}
return 0;
}
Round #4 - 20240913
A. [P10531] Choose
题目大意
个点的树选出最多的个不相交连通块,使得每个连通块内颜色数 。 数据范围:
。
思路分析
考虑自底向上贪心,如果一个子树颜色数
维护每个子树剩余的颜色数直接用 std::unordered_set
启发式合并即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,k,c[MAXN],ans=0;
vector <int> G[MAXN];
unordered_set <int> f[MAXN];
void dfs(int u,int fz) {
f[u].insert(c[u]);
for(int v:G[u]) if(v^fz) {
dfs(v,u);
if(f[u].size()<f[v].size()) swap(f[u],f[v]);
for(int x:f[v]) f[u].insert(x);
}
if((int)f[u].size()>=k) ++ans,f[u].clear();
}
signed main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%d",&c[i]);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
dfs(1,0),printf("%d\n",ans);
return 0;
}
B. [P10813] Swap
题目大意
给定
个操作 ,如果序列 满足 就交换两个元素,求有多少长度为 ,值域为 的 顺次进行 个操作后单调不降。 数据范围:
。
思路分析
考虑 01 分界,即选定一个
然后考虑一个
注意到我们只关心
全集的方案数即为合法序列数,乘一个选颜色的组合数即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
bool ok[1<<18],tk[1<<18];
int f[1<<18],g[1<<18];
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
ll C(int x,int y) {
ll s=1;
for(int i=1;i<=y;++i) s=s*ksm(i)%MOD*(x-i+1)%MOD;
return s;
}
signed main() {
int n,V,m;
scanf("%d%d%d",&n,&V,&m);
vector <array<int,2>> opr(m);
for(auto&z:opr) scanf("%d%d",&z[0],&z[1]);
reverse(opr.begin(),opr.end());
ok[0]=true;
for(int i=1;i<=n;++i) ok[((1<<i)-1)<<(n-i)]=true;
for(auto z:opr) {
int u=z[0]-1,v=z[1]-1;
if(u==v) continue;
memset(tk,0,sizeof(tk));
for(int s=0;s<(1<<n);++s) {
int x=s>>u&1,y=s>>v&1;
tk[s]=x>y?ok[s^(1<<u)^(1<<v)]:ok[s];
}
memcpy(ok,tk,sizeof(ok));
}
f[0]=1;
ll ans=0;
for(int c=1;c<=n&&c<=V;++c) {
memcpy(g,f,sizeof(g));
for(int i=0;i<n;++i) for(int s=0;s<(1<<n);++s) if(s>>i&1) add(f[s],f[s^(1<<i)]);
for(int s=0;s<(1<<n);++s) {
if(!ok[s]) f[s]=0;
else sub(f[s],g[s]);
}
ans=(ans+C(V,c)*f[(1<<n)-1])%MOD;
}
printf("%lld\n",ans);
return 0;
}
C. [P10861] Division
题目大意
给定
以及 ,定义一个区间 的权值为 异或和恰好为 的子区间个数。 将
分成 段,最小化每段区间权值和。 数据范围:
。
思路分析
先考虑如何算一个区间的权值,可以考虑莫队,维护
原问题显然考虑 dp,每次维护
用分治优化 dp 过程,计算区间权值用类似莫队的方法移动指针,可以分析出每次分治时指针的移动总量是
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MAXV=1<<20;
int a[MAXN],X,cnt[MAXV];
ll f[MAXN],g[MAXN],w=0;
void ins(int z) { w+=cnt[z^X],++cnt[z]; }
void ers(int z) { --cnt[z],w-=cnt[z^X]; }
ll qry(int ql,int qr) {
static int l=1,r=0;
while(l>ql) ins(a[--l]);
while(r<qr) ins(a[++r]);
while(r>qr) ers(a[r--]);
while(l<ql) ers(a[l++]);
return w;
}
void DP(int l,int r,int L,int R) {
if(l>r) return ;
int x=(l+r)>>1,p=L;
f[x]=g[p]+qry(p,x);
for(int i=L+1;i<=R&&i<=x;++i) {
ll v=g[i]+qry(i,x);
if(v<f[x]) f[x]=v,p=i;
}
DP(l,x-1,L,p),DP(x+1,r,p,R);
}
signed main() {
int n,k;
scanf("%d%d%d",&n,&k,&X);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),a[i]^=a[i-1];
memset(f,0x3f,sizeof(f)),f[0]=0;
for(int o=1;o<=k;++o) {
memcpy(g,f,sizeof(g));
memset(f,0x3f,sizeof(f));
DP(0,n,0,n);
}
printf("%lld\n",f[n]);
return 0;
}
D. [P11025] Grid
题目大意
给定
的网格图,构造一棵生成树,使得任意切掉某一行或某一列的边后,剩余连通块数量最大值最小。 数据范围:
。
思路分析
先考虑答案至少为
看到
我们观察发现:如果每个点都在他右边和下面的点中恰好选一个点相连,那么一定可以生成树,这是因为一个环一定要有一个拐点同时向右和向下连边。
那么我们只要让每一行向下的边数和每一列向右的边数尽可能平均即可。
可以对每一行都选
容易证明这个构造的答案就是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005;
bool f[MAXN][MAXN];
signed main() {
ios::sync_with_stdio(false);
int n,m; cin>>n>>m;
int k=(n*m-1)/(n+m)+1;
for(int i=0,j=0;i<n;++i) for(int o=0;o<k;++o) f[i][j]=true,j=(j+1)%m;
for(int i=0;i<n;++i) {
for(int j=0;j<m;++j) cout<<"o"<<(f[i][j]?" ":"--");
cout<<"o\n";
for(int j=0;j<m;++j) cout<<(f[i][j]?"|":" ")<<" ";
cout<<"|\n";
}
for(int i=0;i<m;++i) cout<<"o--";
cout<<"o\n";
return 0;
}
E. [P10920] Wildcard
题目大意
给定
位 01 串 ,其中有一些通配符可以任意换成 ,求最大的 使得存在 满足 。 数据范围:
。
思路分析
这个问题显然很难做到 polylog 或者根号,因此考虑 std::bitset
优化。
从大到小枚举 std::bitset
维护为
那么我们检验
对于 std::bitset
字长。
对于
如果一个块非空,那么只有其最长 __builtin_clz
和 __builtin_ctz
维护,需要手写 bitset
。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+5,S=1575;
char s[MAXN];
int n;
ull f[S],g[S],t[S];
bool chk(int x) {
for(int i=0,c=0;i+x<n;++i) {
if(s[i]!='?'&&s[i+x]!='?'&&s[i]!=s[i+x]) c=0;
else ++c;
if(c>=x) return true;
}
return false;
}
void solve() {
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
scanf("%d%s",&n,s);
for(int i=0;i<n;++i) {
if(s[i]=='0') f[i>>6]|=1ull<<(i&63);
if(s[i]=='1') g[i>>6]|=1ull<<(i&63);
}
for(int i=n/2;i>64;--i) {
memset(t,0,sizeof(t));
int ed=(n-i)>>6;
for(int j=0,b=i>>6,r=i&63;j<=ed;++j) {
ull F=f[j+b]>>r,G=g[j+b]>>r;
if(r) F|=f[j+b+1]<<(64-r),G|=g[j+b+1]<<(64-r);
t[j]=(F&g[j])|(G&f[j]);
}
for(int c=(n-i);c<((ed+1)<<6);++c) t[c>>6]|=1ull<<(c&63);
int x=t[0]?__builtin_clzll(t[0]):64;
for(int j=1;j<=ed;++j) {
if(!t[j]) {
if((x+=64)>=i) return printf("%d\n",i),void();
} else {
if(x+__builtin_ctzll(t[j])>=i) return printf("%d\n",i),void();
x=__builtin_clzll(t[j]);
}
}
}
for(int i=min(n/2,64);i;--i) if(chk(i)) return printf("%d\n",i),void();
puts("0");
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*F. [P10656] Distinct
题目大意
给定一个长度为
的元素元素序列 和一个长度为 的元素序列 ,每个元素有颜色和权值,同一序列的元素颜色两两不同。 在两个序列中分别选出一个子区间(可以为空),要求选出所有元素颜色不同,最大化得到的元素和。
数据范围:
。
思路分析
首先我们可以全选序列
不妨假设在
然后考虑在
注意到一个
从而
那么就可以对
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
struct SegmentTree {
array <ll,2> tr[MAXN<<2];
ll tg[MAXN<<2];
void adt(int p,ll k) { tr[p][0]+=k,tg[p]+=k; }
void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
void psu(int p) { tr[p]=max(tr[p<<1],tr[p<<1|1]); }
void init(int l,int r,int p) {
tg[p]=0;
if(l==r) return tr[p]={-inf,l},void();
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
void add(int ul,int ur,ll k,int l,int r,int p) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
} T;
array <ll,5> ANS;
int dlt=0; bool swp=0;
void gans(ll w,int la,int ra,int lb,int rb) {
lb+=dlt,rb+=dlt;
if(swp) swap(la,lb),swap(ra,rb);
if(w>ANS[0]) ANS={w,la,ra,lb,rb};
}
int pos[MAXN<<1],le[MAXN],ri[MAXN];
int sl[MAXN],tl,sr[MAXN],tr;
void solve(ll *a,ll *b,int *c,int *d,int n,int m) {
int z=0;
for(int i=1;i<=n;++i) if(a[i]*2>a[n]) { z=i; break; }
for(int i=1;i<=m;++i) if(c[z]==d[i]) {
if(i>1) solve(a,b,c,d,n,i-1);
if(i<m) dlt=i,solve(a,b+i,c,d+i,n,m-i),dlt=0;
return ;
}
memset(pos,0,sizeof(pos));
for(int i=1;i<=n;++i) pos[c[i]]=i;
T.init(1,m,1);
tl=tr=0,sl[0]=0,sr[0]=0;
for(int i=1;i<=m;++i) {
T.add(i,i,inf,1,m,1);
T.add(1,i,b[i]-b[i-1],1,m,1);
le[i]=0,ri[i]=n;
int x=pos[d[i]];
if(x&&x<z) le[i]=x;
if(x&&x>z) ri[i]=x-1;
for(;tl&&le[sl[tl]]<=le[i];--tl) {
T.add(sl[tl-1]+1,sl[tl],a[le[sl[tl]]],1,m,1);
}
for(;tr&&ri[sr[tr]]>=ri[i];--tr) {
T.add(sr[tr-1]+1,sr[tr],-a[ri[sr[tr]]],1,m,1);
}
T.add(sl[tl]+1,i,-a[le[i]],1,m,1),sl[++tl]=i;
T.add(sr[tr]+1,i,a[ri[i]],1,m,1),sr[++tr]=i;
auto Z=T.tr[1];
int L=*lower_bound(sl+1,sl+tl+1,Z[1]),R=*lower_bound(sr+1,sr+tr+1,Z[1]);
gans(Z[0],le[L]+1,ri[R],Z[1],i);
}
}
ll a[MAXN],b[MAXN];
int c[MAXN],d[MAXN];
signed main() {
ios::sync_with_stdio(false);
int n,m; cin>>n>>m;
for(int i=1;i<=n;++i) cin>>c[i];
for(int i=1;i<=n;++i) cin>>a[i],a[i]+=a[i-1];
for(int i=1;i<=m;++i) cin>>d[i];
for(int i=1;i<=m;++i) cin>>b[i],b[i]+=b[i-1];
gans(a[n],1,n,0,0),gans(b[m],0,0,1,m);
solve(a,b,c,d,n,m),swp=1,solve(b,a,d,c,m,n);
cout<<ANS[0]<<"\n"<<ANS[1]<<" "<<ANS[2]<<"\n"<<ANS[3]<<" "<<ANS[4]<<"\n";
return 0;
}
*G. [P10717] Random
题目大意
给定
个点的树,以及 个集合,第 个集合包含第 个点包含的概率是 。 定义
表示第 个集合对应的斯坦纳树,第 个点的权值为 ,其中 。 求所有点权值乘积的期望。
数据范围:
。
思路分析
考虑
- 状态
: 子树中没有集合中的点。 - 状态
: 子树中有集合中的点,且钦定 子树外还有集合中的点(即 )。 - 状态
: 子树中有集合中的点,且钦定 子树外没有集合中的点(即 )。 - 状态
:合并子树状态时的中间状态,表示仅考虑 子树内节点,已经有 ,即 被选或 至少有两个儿子是状态 ,没有儿子是状态 。
根据如上的定理,我们可以列出所有转移
那么所有转移共
求出当前节点状态后,考虑
然后如果状态为
最后我们可以考虑
那么原问题可以直接 dp,设
但我们注意到
因此如果我们定义状态
预处理出所有转移即可
我们只要在每次将
而还原求出原数组就用状态为
后面的几步转移都可以逐位处理做到
最终答案就是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105,MOD=998244353;
vector <int> G[MAXN];
int n,k,val[MAXN][1<<8],pr[MAXN][8],F[MAXN][1<<16],g[1<<16],e[1<<16];
inline void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
void fwt(int *z,bool ifwt) {
for(int i=0;i<k;++i) for(int s=0;s<(1<<k*2);++s) if((s>>i*2&3)<2) {
(ifwt?sub:add)(z[s|(3<<2*i)],z[s]);
}
}
vector <array<int,3>> Q; //(0,0,0) (0,1,1) (1,0,1) (0,2,2) (2,0,2) (3,3,3)
void dfs(int u,int fz) {
int *f=F[u]; f[0]=1,fwt(f,0);
for(int v:G[u]) if(v^fz) {
dfs(v,u),fwt(F[v],0);
memset(g,0,sizeof(g));
for(auto z:Q) g[z[2]]=(g[z[2]]+1ll*f[z[0]]*F[v][z[1]])%MOD;
memcpy(f,g,sizeof(g));
}
fwt(f,1);
for(int i=0;i<k;++i) {
memset(g,0,sizeof(g));
for(int s=0;s<(1<<k*2);++s) {
g[s]=(g[s]+1ll*f[s]*(1+MOD-pr[u][i]))%MOD;
if((s>>i*2&3)^2) g[s|(3<<i*2)]=(g[s|(3<<i*2)]+1ll*f[s]*pr[u][i])%MOD;
}
memcpy(f,g,sizeof(g));
}
for(int s=0;s<(1<<k*2);++s) f[s]=1ll*f[s]*val[u][e[s]]%MOD;
for(int i=0;i<k;++i) for(int s=0;s<(1<<k*2);++s) if((s>>i*2&3)>2) {
add(f[s^(1<<i*2)],f[s]),add(f[s^(2<<i*2)],f[s]),f[s]=0;
}
}
signed main() {
scanf("%d%d",&n,&k);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
for(int j=0;j<k;++j) for(int i=1;i<=n;++i) scanf("%d",&pr[i][j]);
for(int i=1;i<=n;++i) for(int s=0;s<(1<<k);++s) scanf("%d",&val[i][s]);
vector <array<int,3>> I{{0,0,0},{0,1,1},{1,0,1},{0,2,2},{2,0,2},{3,3,3}};
Q.push_back({0,0,0});
for(int i=0;i<k;++i) {
vector <array<int,3>> P;
for(auto s:Q) for(auto z:I) {
P.push_back({s[0]<<2|z[0],s[1]<<2|z[1],s[2]<<2|z[2]});
}
Q.swap(P);
}
for(int s=0;s<(1<<k*2);++s) for(int i=0;i<k;++i) e[s]|=(s>>i*2&1)<<i;
dfs(1,0);
int ans=0;
for(int s=0;s<(1<<k*2);++s) if(!e[s]) add(ans,F[1][s]);
printf("%d\n",ans);
return 0;
}
*H. [P10546] Interactive
题目大意
交互器有
个点 条边的有向图,将图视为无向图后是一棵树。 你可以进行如下操作,每次可以翻转若干条边,你会知道这种情况下每个点
能被多少个点到达,记为 。 请在
次操作内还原整张图,即确定每条边的起点和终点。 数据范围:
。
思路分析
记询问次数
对于树上问题,考虑从叶子出发逐步剥离每个点。
我们要先求出叶子,注意到如果某个叶子
假如每条边都随机,那么有
考虑一个经典的技巧,我们对每条边,随机选择
那么对于一个叶子结点
对于一个非叶子节点,他的两条出边至少有一个时刻不同向,那么这个点满足
那么这样就能每次求出一个叶子结点
确定
然后考虑找父亲,注意到
考虑什么时候会将一个错误的
那么我们知道所有情况中
因此判断错误的概率为
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int Q=50,MAXN=10005;
const ll A=(1ll<<Q)-1;
mt19937 rnd(time(0));
ll gen() {
vector <int> p;
for(int i=0;i<Q;++i) p.push_back(i);
shuffle(p.begin(),p.end(),rnd);
ll z=0;
for(int i=0;i<Q/2;++i) z|=1ll<<p[i];
return z;
}
int n,f[Q+5][MAXN],w[Q+5][MAXN],c[MAXN],U[MAXN],V[MAXN];
ll S[MAXN];
bool del[MAXN];
unordered_map <ll,int> E;
signed main() {
cin>>n;
for(int i=1;i<n;++i) {
ll x=gen();
while(E.count(x)) x=gen();
S[i]=x,E[x]=i,E[A^x]=-i;
}
for(int i=0;i<Q;++i) {
cout<<"? ";
for(int u=1;u<n;++u) cout<<(S[u]>>i&1);
cout<<endl;
for(int u=1;u<=n;++u) cin>>f[i][u],w[i][u]=1,c[u]+=(f[i][u]==1);
}
for(int o=1;o<n;++o) {
int x=0,y=0;
for(int u=1;u<=n;++u) if(!del[u]&&c[u]==Q/2) { x=u; break; }
ll I=0; del[x]=true;
for(int i=0;i<Q;++i) if(f[i][x]>w[i][x]) I|=1ll<<i;
for(int u=1;u<=n;++u) if(!del[u]) {
bool flg=1;
for(int i=0;i<Q;++i) if((I>>i&1)&&f[i][x]-w[i][x]!=f[i][u]) {
flg=0; break;
}
if(!flg) continue;
y=u; break;
}
for(int i=0;i<Q;++i) if(!(I>>i&1)) w[i][y]+=w[i][x],c[y]+=(w[i][y]==f[i][y]);
int z=E[I];
if(z>0) U[z]=x,V[z]=y;
else U[-z]=y,V[-z]=x;
}
cout<<"! ";
for(int i=1;i<n;++i) cout<<U[i]<<" "<<V[i]<<" ";
cout<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!