11月CWOI杂题
要考 noip 了啊。
C0367 【1102 B组】模拟测试
A 【1102 B组】Triple
送分题。枚举 \(j\) 直接数点即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
struct fio{
#define isdigit(x) (x >= '0' && x <= '9')
char buf[1 << 20], *p1, pbuf[1 << 20], *p2, *pp;
fio() : p1(buf), p2(buf), pp(pbuf){}
~fio(){fwrite(pbuf, 1, pp - pbuf, stdout);}
inline char gc(){return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1 ++;}
inline void pc(const char &c){if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;*pp ++ = c;}
inline bool blank(char ch){return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';}
template <class T> inline void read(T &x){
long double tmp = 1;bool sign = 0;char ch = gc();x = 0;
for (;!isdigit(ch);ch = gc()) if (ch == '-') sign = 1;
for (;isdigit(ch);ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.') for (ch = gc();isdigit(ch);ch = gc()) tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
inline void read(char *s){
char ch = gc();for (;blank(ch);ch = gc());
for (;!blank(ch);ch = gc()) *s ++ = ch;*s = 0;
}
inline void read(char &c){for (c = gc();blank(c);c = gc());}
template <class T> inline void write(T x){
if (x < 0) x = -x, pc('-');int sta[30];int top = 0;
do sta[top ++] = x % 10, x /= 10;while (x);
while (top) pc(sta[-- top] + '0');
}
}io;
int n,ans,a[500005],pos[500005];vector<int>v[500005];
struct BIT{
int c[500005];
void add(int x,int v){
for(;x<=n;x+=x&-x)c[x]+=v;
}
int ask(int x){
int res=0;
for(;x;x-=x&-x)res+=c[x];
return res;
}
int ask(int l,int r){
if(l>r)return 0;
return ask(r)-ask(l-1);
}
}Tr;
signed main(){
io.read(n);
for(int i=1;i<=n;i++)io.read(a[i]),v[a[i]].push_back(i),pos[i]=(int)v[a[i]].size()-1;
for(int i=1;i<=n;i++){
ans+=((int)v[a[i]].size()-1-pos[i])*Tr.ask(v[a[i]].front(),n);
Tr.add(a[i],1);
}
printf("%lld\n",ans);
return 0;
}
B 【1102 B组】Traversal
比较简单的找规律?
考虑分成最终从左/右/上/下进入 \((x,y)\) 讨论。记 \(f_{i,0/1}\) 表示从 \((1,1)\) 开始走完 \((1,1)\) 到 \((3,i)\) 间的所有格子,且最终在 \((1/3,i)\) 的方案数。记一个 \(g\) 表示前缀和,那么 \(f_{i,j}=g_{i-1,j\oplus 1}\)。令 \(k=\lfloor\dfrac{n-y}{2}\rfloor\)。
- 左:
- 右:
- 上:
- 下:
其实 \(\forall i>1,f_{i,0/1}=2^{i-1}\),所以应该可以进一步化简,但我懒得搞了。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6,mod=998244353;
struct fio{
#define isdigit(x) (x >= '0' && x <= '9')
char buf[1 << 20], *p1, pbuf[1 << 20], *p2, *pp;
fio() : p1(buf), p2(buf), pp(pbuf){}
~fio(){fwrite(pbuf, 1, pp - pbuf, stdout);}
inline char gc(){return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1 ++;}
inline void pc(const char &c){if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;*pp ++ = c;}
inline bool blank(char ch){return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';}
template <class T> inline void read(T &x){
long double tmp = 1;bool sign = 0;char ch = gc();x = 0;
for (;!isdigit(ch);ch = gc()) if (ch == '-') sign = 1;
for (;isdigit(ch);ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.') for (ch = gc();isdigit(ch);ch = gc()) tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
inline void read(char *s){
char ch = gc();for (;blank(ch);ch = gc());
for (;!blank(ch);ch = gc()) *s ++ = ch;*s = 0;
}
inline void read(char &c){for (c = gc();blank(c);c = gc());}
template <class T> inline void write(T x){
if (x < 0) x = -x, pc('-');int sta[30];int top = 0;
do sta[top ++] = x % 10, x /= 10;while (x);
while (top) pc(sta[-- top] + '0');
}
}io;
int f[2000005][2],g[2000005][2],pw[2000005],ans;
void solve(){
int n,x,y,res=0;io.read(n);io.read(x);io.read(y);
if(y!=1){
if(x==1&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*g[y-2][1]%mod)%mod;
if(x==2&&(n-y)%2==1)res=(res+1ll*pw[(n-y-1)/2]*(g[y-2][0]+g[y-2][1])%mod)%mod;
if(x==3&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*g[y-2][0]%mod)%mod;
}
if(y!=n){
if(x==1&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][1]%mod)%mod;
if(x==3&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][0]%mod)%mod;
}
if(x!=1){
if(x==2&&(n-y)%2==1)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][1]%mod)%mod;
if(x==3&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][0]%mod)%mod;
}
if(x!=3){
if(x==1&&(n-y)%2==0)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][1]%mod)%mod;
if(x==2&&(n-y)%2==1)res=(res+1ll*pw[(n-y-1)/2]*f[y-1][0]%mod)%mod;
}
ans^=res;
}
signed main(){
f[0][0]=1,g[0][0]=1,pw[0]=1;
for(int i=1;i<=N;i++){
f[i][0]=g[i-1][1],f[i][1]=g[i-1][0];
g[i][0]=(g[i-1][0]+f[i][0])%mod;
g[i][1]=(g[i-1][1]+f[i][1])%mod;
pw[i]=2ll*pw[i-1]%mod;
}
int T;io.read(T);
while(T--){
solve();
}
printf("%d\n",ans);
return 0;
}
C 【1102 B组】DESTRUCTION 3,2,1
神秘构造。link。
D 【1102 B组】烤枯磊忒
C0369 【1103 B组】模拟测试
A 【1103 B组】young
发现答案就是两棵树上父亲不同的点数。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int Fa[200005],Fa1[200005],Fa2[200005];
void init(int u,int fa){
Fa[u]=fa;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
init(v,u);
}
}
signed main(){
int n=read(),ans=0;
tot=0;for(int i=1;i<=n;i++)head[i]=0;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
init(1,0);for(int i=1;i<=n;i++)Fa1[i]=Fa[i];
tot=0;for(int i=1;i<=n;i++)head[i]=0;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
init(1,0);for(int i=1;i<=n;i++)Fa2[i]=Fa[i];
for(int i=1;i<=n;i++)ans+=(Fa1[i]!=Fa2[i]);
printf("%lld\n",ans);
return 0;
}
B 【1103 B组】bamboo
考虑点 \(i\) 会和前面的点产生多少对逆序对。记 \(c_i\) 表示 \(a_i\) 有多少种选法,\(C=\prod c_i\),那么答案就是 \(\sum\limits_{a_i}\sum\limits_{j=1}^{i-1}\sum\limits_{a_j}\dfrac{C}{c_ic_j}[a_j<a_i]\)。假如 \(i\) 没有限制,发现一个 \(a_j=k\) 的情况会被计算 \(k-1\) 遍,可以维护 \(\sum\dfrac{k-1}{c_j}\)。加上限制之后,我们发现这个相当于一个全局加单点减,把贡献减去即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
int n,m,k,cnt[200005];
struct BIT1{
int all,c[200005];
void addall(int v){
all=(all+v)%mod;
}
void add(int x,int v){
for(;x<=m;x+=x&-x)c[x]=(c[x]+v)%mod;
}
int ask(int x){
int res=((x-1)*x/2)%mod*all%mod;
for(;x;x-=x&-x)res=(res+c[x])%mod;
return res;
}
int ask(int l,int r){
if(l>r)return 0;
return (ask(r)-ask(l-1)+mod)%mod;
}
}Tr1;
struct BIT2{
int all,c[200005];
void addall(int v){
all=(all+v)%mod;
}
void add(int x,int v){
for(;x<=m;x+=x&-x)c[x]=(c[x]+v)%mod;
}
int ask(int x){
int res=all*x%mod;
for(;x;x-=x&-x)res=(res+c[x])%mod;
return res;
}
int ask(int l,int r){
if(l>r)return 0;
return (ask(r)-ask(l-1)+mod)%mod;
}
}Tr2;
vector<int>v[200005];
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++)cnt[i]=m;
for(int i=1,x,y;i<=k;i++){
x=read(),y=read(),v[x].push_back(y),cnt[x]--;
}
int ans=0;
for(int i=1;i<=n;i++){
int ivc=qpow(cnt[i],mod-2),tmp=0;
tmp=(tmp+Tr1.ask(1,m))%mod;
for(auto x:v[i])tmp=(tmp-Tr2.ask(x+1,m)+mod)%mod;
ans=(ans+tmp*ivc%mod)%mod;
Tr1.addall(ivc),Tr2.addall(ivc);
for(auto x:v[i])Tr1.add(x,mod-ivc*(x-1)%mod),Tr2.add(x,mod-ivc);
}
printf("%lld\n",ans);
return 0;
}
C 【1103 B组】follow
有点妙啊。
先考虑只有一个 \(k\) 的时候怎么做。可以双指针预处理每个点 \(i\) 为开头的极长合法段 \([i,f_i)\),然后倍增跳就可以做到单次 \(\mathcal{O}(n\log n)\)。考虑优化。发现一个性质:任取一个 \(i\),\([i+1,f_i]\) 内必有一个点会成为一个连续段的开头。找到 \(f_i-i\) 最小的 \(i\),枚举起点,暴力往后跳即可。此时因为 \(i\) 是最小,所以跳的时候步长至少为 \(f_i-i\),复杂度 \(\mathcal{O}(n)\)。
怎么求 \(k\in[1,n]\) 的答案?考虑根号分治。当 \(k\le T\) 时暴力求,否则答案 \(\le \dfrac{n}{T}\),枚举答案 \(i\),二分找到它对应的 \(k\) 的区间。复杂度 \(\mathcal{O}(Tn+\dfrac{n^2\log n}{T})\),取 \(T=\sqrt{n\log n}\) 时复杂度为 \(\mathcal{O}(n\sqrt{n\log n})\)。
但是这个题比较卡常,可能需要更优的计算方法。具体地,记 \(len_i\) 表示从 \(i\) 开始跳 \(f_i\) 直到跳过 \(n\) 的步数,\(pos_i\) 表示跳过 \(n\) 之后停在什么位置。然后我们枚举 \(i\in[1,f_i]\) 更新答案,如果 \(pos_i<i\) 说明还需要多跳一步。复杂度也是 \(\mathcal{O}(n)\) 的,但常数很小。然后记 \(mx\) 表示 \(a\) 中出现次数最多的数的出现次数,当 \(k\ge mx\) 时上述方法会出错,需要手动赋一下 1。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,T,mx,a[200005],ans[200005],c[200005],nxt[200005],len[200005],pos[200005];
int calc(int k){
if(ans[k])return ans[k];
for(int i=1,j=1;i<=n;i++){
while(c[a[j]]+1<=k)c[a[j]]++,j=((j==n)?1:j+1);
nxt[i]=j,c[a[i]]--;
}
for(int i=1;i<=n;i++)c[i]=0;
for(int i=n;i>=1;i--){
if(nxt[i]<i)len[i]=1,pos[i]=nxt[i];
else len[i]=len[nxt[i]]+1,pos[i]=pos[nxt[i]];
}
int res=inf;
for(int i=1;i<=nxt[1];i++){
res=min(res,len[i]+(pos[i]<i));
}
return ans[k]=res;
}
signed main(){
n=read(),m=read(),T=min(n,1200),mx=0;
for(int i=1;i<=n;i++)a[i]=read(),c[a[i]]++;
for(int i=1;i<=n;i++)mx=max(mx,c[i]);
for(int i=1;i<=n;i++)c[i]=0;
for(int i=1;i<=min(mx-1,T);i++)ans[i]=calc(i);
for(int i=mx;i<=n;i++)ans[i]=1;
for(int i=ans[T],l=T+1,r=n,res=n;i>=1;i--){
if(calc(l)<i)continue;
if(l+1<=n&&calc(l+1)!=i){ans[l]=i;l++;continue;}
int lst=l;
while(l<=r){
int mid=(l+r)>>1;
if(calc(mid)>=i)res=mid,l=mid+1;
else r=mid-1;
}
for(int j=lst;j<=res;j++)ans[j]=i;
l=res+1,r=n,res=n;
}
for(int i=1,x;i<=m;i++)x=read(),printf("%d\n",ans[x]);
return 0;
}
D 【1103 B组】heart
考虑 dp。假设当前在 \(i\),记 \(f_j\) 表示 \(\min_{k\in path(i,j)}^{p_k>p_j}p_k\)。那么现在操作变成求 \(p_j<p_i,f_j>p_i\) 的所有 \(j\) 的 dp 值之和,以及把所有 \(p_i>p_j\) 的位置的 \(f\) 与 \(p_i\) 取最小值。记一个 \(a_x\) 表示 \(p_j=x\) 的 dp 值之和,\(b_x\) 表示 \(p_j=x\) 的 \(f\),发现区间取最小值,区间求和可以用吉司机线段树维护,加上一个线段树合并。因为每次操作完后都会紧跟一个取 min,且线段树合并不会影响势能,所以这个复杂度是对的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9,mod=998244353;
struct fio{
#define isdigit(x) (x >= '0' && x <= '9')
char buf[1 << 20], *p1, pbuf[1 << 20], *p2, *pp;
fio() : p1(buf), p2(buf), pp(pbuf){}
~fio(){fwrite(pbuf, 1, pp - pbuf, stdout);}
inline char gc(){return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1 ++;}
inline void pc(const char &c){if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;*pp ++ = c;}
inline bool blank(char ch){return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';}
template <class T> inline void read(T &x){
long double tmp = 1;bool sign = 0;char ch = gc();x = 0;
for (;!isdigit(ch);ch = gc()) if (ch == '-') sign = 1;
for (;isdigit(ch);ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.') for (ch = gc();isdigit(ch);ch = gc()) tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
inline void read(char *s){
char ch = gc();for (;blank(ch);ch = gc());
for (;!blank(ch);ch = gc()) *s ++ = ch;*s = 0;
}
inline void read(char &c){for (c = gc();blank(c);c = gc());}
template <class T> inline void write(T x){
if (x < 0) x = -x, pc('-');int sta[30];int top = 0;
do sta[top ++] = x % 10, x /= 10;while (x);
while (top) pc(sta[-- top] + '0');
}
}io;
struct edge{
int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int root[500005];
struct segtree{
#define ls c[p].lc
#define rs c[p].rc
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int lc,rc,tag,mx,se,smx;
}c[10000005];
int T;
void pushup(int p){
c[p].mx=max(c[ls].mx,c[rs].mx);c[p].se=c[p].smx=0;
if(ls&&c[ls].mx==c[p].mx)c[p].smx=(c[p].smx+c[ls].smx)%mod;
if(rs&&c[rs].mx==c[p].mx)c[p].smx=(c[p].smx+c[rs].smx)%mod;
if(ls&&c[ls].mx!=c[p].mx)c[p].se=max(c[p].se,c[ls].mx);
if(ls&&c[ls].se!=c[p].mx)c[p].se=max(c[p].se,c[ls].se);
if(rs&&c[rs].mx!=c[p].mx)c[p].se=max(c[p].se,c[rs].mx);
if(rs&&c[rs].se!=c[p].mx)c[p].se=max(c[p].se,c[rs].se);
}
void pushdown(int p){
if(!c[p].tag)return;
if(ls&&c[ls].mx>c[p].tag)c[ls].tag=c[ls].mx=c[p].tag;
if(rs&&c[rs].mx>c[p].tag)c[rs].tag=c[rs].mx=c[p].tag;
c[p].tag=0;
}
void add(int l,int r,int &p,int x,int k){
if(!p)p=++T;
if(l==r){c[p].mx=inf,c[p].se=-inf,c[p].smx=k;return;}
int mid=(l+r)>>1;pushdown(p);
if(x<=mid)add(lson,x,k);
else add(rson,x,k);
pushup(p);
}
int sol(int l,int r,int &p,int L,int R,int k){
if(!p)p=++T;
if(c[p].mx<k)return 0;
if(L<=l&&r<=R&&c[p].mx>k&&c[p].se<=k){c[p].mx=c[p].tag=k;return c[p].smx;}
if(l==r)return 0;
int mid=(l+r)>>1,res=0;pushdown(p);
if(L<=mid)res=(res+sol(lson,L,R,k))%mod;
if(R>mid)res=(res+sol(rson,L,R,k))%mod;
pushup(p);return res;
}
void merge(int l,int r,int &p,int q){
if(p==0||q==0){p=p+q;return;}
if(l==r){
if(c[p].mx>c[q].mx)c[p].se=max(c[p].se,c[q].mx);
else if(c[p].mx==c[q].mx)c[p].se=max(c[p].se,c[q].se),c[p].smx=(c[p].smx+c[q].smx)%mod;
else c[p].se=max(c[p].mx,c[q].se),c[p].mx=c[q].mx,c[p].smx=c[q].smx;
return;
}
int mid=(l+r)>>1;pushdown(p);pushdown(q);
merge(lson,c[q].lc);merge(rson,c[q].rc);
pushup(p);
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int n,a[500005],f[500005];
void dfs(int u,int fa){
f[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);f[u]=(f[u]+f[v])%mod;
Tr.merge(1,n,root[u],root[v]);
}
f[u]=(f[u]+Tr.sol(1,n,root[u],1,a[u],a[u]))%mod;
Tr.add(1,n,root[u],a[u],f[u]);
}
signed main(){
io.read(n);for(int i=1,u,v;i<n;i++)io.read(u),io.read(v),add(u,v),add(v,u);
for(int i=1;i<=n;i++)io.read(a[i]);
dfs(1,0);for(int i=1;i<=n;i++)io.write(f[i]),io.pc('\n');
return 0;
}
C0374 【1106 B组】模拟测试
题出得挺烂的,简单改下。
A 【1106 B组】登山
结论题,但数据很弱,什么做法都能过。不打算仔细想。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int h[5000005],a[5000005],pmx[5000005],pmn[5000005],smx[5000005],smn[5000005];
signed main(){
int n=read();
for(int i=1;i<=n;i++)h[i]=read();
int m=0,d=h[1];
for(int i=2;i<=n;i++){
if(h[i]-h[i-1]==0)continue;
if((h[i]-h[i-1])*d>=0)d=h[i]-h[i-1];
else a[++m]=h[i-1],d=h[i]-h[i-1];
}
a[++m]=h[n];
pmx[0]=0;for(int i=1;i<=m;i++)pmx[i]=max(pmx[i-1],a[i]);
pmn[0]=0;for(int i=1;i<=m;i++)pmn[i]=min(pmn[i-1],a[i]);
smx[m+1]=0;for(int i=m;i>=1;i--)smx[i]=max(smx[i+1],a[i]);
smn[m+1]=0;for(int i=m;i>=1;i--)smn[i]=min(smn[i+1],a[i]);
int l=0,r=m+1,flag=1;
while(l!=r){
if(a[l+1]==a[r-1]){l++,r--;continue;}
if((pmn[l]<=a[r-1]&&a[r-1]<=pmx[l])||(min(a[l+1],a[l])<=a[r-1]&&a[r-1]<=max(a[l+1],a[l])))r--;
else if((smn[r]<=a[l+1]&&a[l+1]<=smx[r])||(min(a[r],a[r-1])<=a[l+1]&&a[l+1]<=max(a[r],a[r-1])))l++;
else{flag=0;break;}
}
if(flag)return puts("Yes"),0;
puts("No");
return 0;
}
B 【1106 B组】蝴蝶变换
乱搞的构造题,不会。
C 【1106 B组】爬梯高手
考虑暴力。枚举开始层和结束层,然后拆点跑 flow 即可。因为最大流等于最小割,问题等价于从 \(l\) 层出发到 \(r\) 层结束,删去最少的点使得从最上面到最下面不连通。考虑 dp:从小到大枚举 \(r\),\(f_{l,i,j}\) 表示从第 \(l\) 层开始,和第 \(r\) 层的 \(i\) 点集中的点联通,删去 \(j\) 个点是否可行。显然 \(j\le m\)。这样的状态定义还是太浪费了,发现每个 \(l\) 对应的最小可行 \(j\) 是递减的,于是重定义 \(f_{i,j}\) 表示联通点集为 \(i\),最小操作次数 \(\le j\) 的最大的 \(l\),那么答案就是 \(\sum\limits_{j=0}^{m-1}(r-1-f_{0,j})\),即答案 \(\ge 1\) 的 + 答案 \(\ge 2\) 的 \(\ldots\)
转移就是如果不删点直接拓展过后转过来,否则可以 \(3^m\) 枚举子集。但是这有点慢,考虑分步转移,一次只看一个点,这样就是 \(m2^m\) 的了。细节较多。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,e[12],f[515][12],g[515][12];ll ans;char s[12];
int exp(int msk){
int res=0;
for(int i=0;i<m;i++)if((msk>>i)&1)res|=e[i];
return res;
}
signed main(){
scanf("%d%d",&n,&m);
for(int j=0;j<(1<<m);j++){
for(int k=0;k<=m;k++)f[j][k]=((k>=m-__builtin_popcount(j))?1:0);
}
for(int i=2;i<=n;i++){
for(int j=0;j<m;j++){
scanf("%s",s);e[j]=0;
for(int k=0;k<m;k++)if(s[k]=='1')e[j]|=(1<<k);
}
for(int j=0;j<(1<<m);j++){
for(int k=0;k<=m;k++)g[j][k]=f[j][k],f[j][k]=((k>=m-__builtin_popcount(j))?i:0);
}
for(int j=0;j<(1<<m);j++){
int t=exp(j);
for(int k=0;k<=m;k++)f[t][k]=max(f[t][k],g[j][k]);
}
for(int c=0;c<m;c++){
for(int j=0;j<(1<<m);j++){
for(int k=j;k;k-=k&-k){
f[j^(k&-k)][c+1]=max(f[j^(k&-k)][c+1],f[j][c]);
}
}
}
for(int j=0;j<m;j++)ans+=max(0,i-1-f[0][j]);
}
printf("%lld\n",ans);
return 0;
}
D 【1106 B组】Border的第六种求法
论文题。xtq 的 2023 年集训队论文《一类基础子串数据结构》。
C0376 【1107 B组】模拟测试
对不起,我以为昨天的题已经够逆天了,没想到还有高手。😅
A 【1107 B组】dp
oeis A014225。找规律题,如果 \(n>4\) 就无解。题解证明如下:
设 \(x=\dfrac{\sqrt{5}-1}{2}\),将一个棋子的权值定义为 \(x^d\)(\(d\) 为这个棋子到终点的距离),设 \(S=\sum x^d\)。易证每次操作后,\(S\) 的值不会增加。当 \(n>4\) 时,棋子的初始权值和不可能达到 \(1\),所以无解。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
int T=read();
while(T--){
int n=read();
if(n==0)continue;
else if(n==1)puts("2");
else if(n==2)puts("4");
else if(n==3)puts("8");
else if(n==4)puts("20");
else puts("-1");
}
return 0;
}
B 【1107 B组】线段树
有点智慧?考虑直接维护,加的时候可以打 tag,难点在于如何维护 \([x+1,y]\) 的最小值。考虑在 \([x+1,y]\) 中间找一个点 \(p\),维护从 \(p\) 往左的和往右的最小值,询问的时候拼起来,当 \(x+1\) 或 \(y\) 碰到 \(p\) 的时候取 \(p=\lfloor\dfrac{x+1+y}{2}\rfloor\) 暴力重构即可。容易发现这样复杂度均摊是对的。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18,V=5e14;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
unsigned long long Seed;
long long W,X,Y,Z;
unsigned long long HRAND()
{
Seed^=Seed<<9;
Seed^=Seed>>11;
Seed^=Seed<<15;
return Seed;
}
void Gen(int &op,int &k)
{
long long OP=HRAND()%(X+Y+2*Z);
if(OP<X) op=1+HRAND()%2,k=0;
else if(OP<X+Y) op=3+HRAND()%2,k=HRAND()%(2*W)-W;
else if(OP<X+Y+Z) op=5+HRAND()%2,k=0;
else op=7+HRAND()%2,k=0;
}
int n,m;ll ans,a[10000005],P[10000005],S[10000005];
signed main(){
scanf("%d%d%llu%lld%lld%lld%lld",&n,&m,&Seed,&W,&X,&Y,&Z);
int I=0,x=0,y=n-2,p=(x+1+y)/2;ll lmn=0,rmn=0,all=0,tl=0,tr=0;
while(m--){
int op,k;Gen(op,k);I++;
if(op==1){
if(x+1==y){
for(int i=0;i<=x;i++)a[i]+=tl;
tl=0;lmn=a[0],x=0,p=(x+1+y)/2;
P[p]=a[p];for(int i=p-1;i>=x+1;i--)P[i]=min(P[i+1],a[i]);
S[p]=a[p];for(int i=p+1;i<=y;i++)S[i]=min(S[i-1],a[i]);
}
else if(x+1+1<=p){
x++,a[x]-=tl,lmn=min(lmn,a[x]);
}
else{
x++,a[x]-=tl,lmn=min(lmn,a[x]);p=(x+1+y)/2;
P[p]=a[p];for(int i=p-1;i>=x+1;i--)P[i]=min(P[i+1],a[i]);
S[p]=a[p];for(int i=p+1;i<=y;i++)S[i]=min(S[i-1],a[i]);
}
}
else if(op==2){
if(x==y-1){
for(int i=y+1;i<=n-1;i++)a[i]+=tr;
tr=0;rmn=a[n-1],y=n-2,p=(x+1+y)/2;
P[p]=a[p];for(int i=p-1;i>=x+1;i--)P[i]=min(P[i+1],a[i]);
S[p]=a[p];for(int i=p+1;i<=y;i++)S[i]=min(S[i-1],a[i]);
}
else if(y-1>=p){
a[y]-=tr,rmn=min(rmn,a[y]);y--;
}
else{
a[y]-=tr,rmn=min(rmn,a[y]);y--;p=(x+1+y)/2;
P[p]=a[p];for(int i=p-1;i>=x+1;i--)P[i]=min(P[i+1],a[i]);
S[p]=a[p];for(int i=p+1;i<=y;i++)S[i]=min(S[i-1],a[i]);
}
}
else if(op==3){
tl+=k;
}
else if(op==4){
all+=k,tr-=k;
}
else if(op==5){
ans^=1ll*(I%998)*(all+lmn+tl+V);
}
else if(op==6){
ll res=min(lmn+tl,min(P[x+1],S[y]));
ans^=1ll*(I%998)*(all+res+V);
}
else if(op==7){
ll res=min(rmn+tr,min(P[x+1],S[y]));
ans^=1ll*(I%998)*(all+res+V);
}
else{
ans^=1ll*(I%998)*(all+rmn+tr+V);
}
}
printf("%lld\n",ans);
return 0;
}
C 【1107 B组】博弈论
显然 B 等概率选择左右手是最优的,不然 A 可以把更难猜的放到 B 更小概率看的一只手上。先离散化。考虑记一个 \(f_i,g_i,h_i\) 表示在知道一个球是 \(i\) 之后,猜小于/等于/大于的概率,再记 \(t_i=[\sum\limits_{j=1}^n[a_j=i]>1]\),\(s_i\) 表示前缀和。那么有 \(f_i+g_i+h_i=1\),\(g_i\ge t_iP\),\(\dfrac{f_{i+1}+h_{i}}{2}\ge P\)。显然 \(g_i=t_iP\) 时可以使 \(f,h\) 最大,更可能合法,于是 \(h_i=1-t_iP-f_i\)。代入得 \(\dfrac{f_{i+1}+1-t_iP-f_i}{2}\ge P\),即 \(f_i\le f_{i+1}+1-t_iP-2P\);同时 \(f_i,h_i\ge 0\),于是 \(f_i\le 1-t_iP\),即 \(f_i=\min(f_{i+1}+1-t_iP-2P,1-t_iP)=\min(f_{i+1}-2P,0)+1-t_iP\)。考虑如何找到让 \(f_i\ge 0\) 的最大的 \(P\)。这个东西等价于
为什么?你把上面取 min 当成有一个人在和你作对,他可以选择取第一个或第二个,目标是最小化 \(f_j\)。考虑 \(i\) 是最后一次取 min 的位置,然后就是上面那个了。化一下就是
可以维护 \((2i+s_{i-1},i)\) 的下凸壳,二分即可。发现这个凸壳很小啊,并不好卡,你直接暴力遍历也能过。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=1ll*b*b%mod)if(p&1)res=1ll*res*b%mod;
return res;
}
struct Fac{
int p,q;
bool operator <(const Fac &b)const{
return 1ll*p*b.q<1ll*b.p*q;
}
};
ll a[10000005];int p[10000005],c[10000005],s[10000005],q[10000005];
void solve(){
int n=read(),tot=0;ll a0=read(),A=read(),B=read(),C=read(),M=read();a[0]=a0%M;
for(int i=1;i<=n;i++)a[i]=(1ll*A*a[i-1]%M*a[i-1]%M+1ll*B*a[i-1]%M+C)%M+1;
for(int i=1;i<=n;i++)c[a[i]]++;
int L=1,R=0;Fac ans=(Fac){1,1};
for(int i=1;i<=M;i++){
if(!c[i])continue;
tot++,s[tot]=s[tot-1]+(c[i]>1),c[i]=0;
for(int j=L;j<=R;j++)ans=min(ans,(Fac){tot+1-q[j],(2*tot+s[tot])-(2*q[j]+s[q[j]-1])});
while(L<R&&1ll*(q[R]-q[R-1])*((2*tot+s[tot-1])-(2*q[R]+s[q[R]-1]))<=1ll*(tot-q[R])*((2*q[R]+s[q[R]-1])-(2*q[R-1]+s[q[R-1]-1])))R--;
q[++R]=tot;
}
printf("%lld\n",1ll*ans.p*qpow(ans.q,mod-2)%mod);
}
signed main(){
int T=read();
while(T--){
solve();
}
return 0;
}
D 【1107 B组】图论
C0380 【1109 B组】模拟测试
A 【1109 B组】排序题
发现按 \(w\) 从大到小排序后只有前面的可能影响后面的,从前往后染一遍即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Point{
int x,w;
}a[500005];
int cmp(Point x,Point y){
return x.w>y.w;
}
int n,tot,ans,b[500005];
struct BIT{
int c[500005];
void build(){
for(int i=1;i<=tot;i++)c[i]=-inf;
}
void add(int x,int v){
for(;x<=tot;x+=x&-x)c[x]=max(c[x],v);
}
int ask(int x){
int res=-inf;
for(;x;x-=x&-x)res=max(res,c[x]);
return res;
}
}Tr1,Tr2;
signed main(){
n=read(),tot=0;
for(int i=1;i<=n;i++)a[i].x=b[++tot]=read(),a[i].w=read();
sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;i++)a[i].x=lower_bound(b+1,b+tot+1,a[i].x)-b;
sort(a+1,a+n+1,cmp);Tr1.build(),Tr2.build();
for(int i=1;i<=n;i++){
if(Tr1.ask(a[i].x)>=a[i].w+b[a[i].x])continue;
if(Tr2.ask(tot-a[i].x+1)>=a[i].w-b[a[i].x])continue;
ans++;Tr1.add(a[i].x,a[i].w+b[a[i].x]);Tr2.add(tot-a[i].x+1,a[i].w-b[a[i].x]);
}
printf("%lld\n",ans);
return 0;
}
B 【1109 B组】预处理器
先转化成没有奇偶性限制,每个数在 \([L_i,R_i]\) 内随便取。考虑容斥,然后每个数就只有下界没有上界了,插板组合算一下。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,s,t,mod,a[20],b[20],p[20];
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
unordered_map<int,int>mp;
for(int i=n;i>=n-m+1;i--){
int x=i;
for(int j=2;j*j<=x;j++){
int cnt=0;
while(x%j==0)x/=j,cnt++;
if(cnt)mp[j]+=cnt;
}
if(x>1)mp[x]++;
}
for(int i=1;i<=m;i++){
int x=i;
for(int j=2;j*j<=x;j++){
int cnt=0;
while(x%j==0)x/=j,cnt++;
if(cnt)mp[j]-=cnt;
}
if(x>1)mp[x]--;
}
int res=1;
for(auto x:mp)res=res*qpow(x.first,x.second)%mod;
return res;
}
int solve(int lim){
int res=0;
for(int i=0;i<(1ll<<n);i++){
int tmp=lim,o=((__builtin_popcount(i)&1ll)?-1:1);
for(int j=0;j<n;j++)if((i>>j)&1ll)tmp-=b[j]+1;
res=(res+o*C(tmp+n,n)+mod)%mod;
}
return res;
}
signed main(){
n=read(),s=read(),t=read(),mod=read();
for(int i=0;i<n;i++){
a[i]=read(),b[i]=read(),p[i]=read();
}
for(int i=0;i<n;i++){
if(p[i]==1)a[i]--,b[i]--,s--,t--;
}
s=(s+1)/2,t=t/2;
if(s>t)return puts("0"),0;
for(int i=0;i<n;i++){
a[i]=(a[i]+1)/2,b[i]=b[i]/2;
if(a[i]>b[i])return puts("0"),0;
}
for(int i=0;i<n;i++){
s-=a[i],t-=a[i],b[i]-=a[i];
}
if(t<0)return puts("0"),0;
printf("%lld\n",(solve(t)-solve(s-1)+mod)%mod);
return 0;
}
C 【1109 B组】树
直接统计困难,考虑在路径与高的交点计算。换根维护每个点为根时每个儿子子树内最长的链的长度和数量,分类讨论一下即可。细节较多。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
struct Info{
ll mx,cnt;
Info operator +(const Info &b)const{
if(cnt==0)return b;
if(b.cnt==0)return (Info){mx,cnt};
if(mx>b.mx)return (Info){mx,cnt};
if(mx<b.mx)return b;
return (Info){mx,cnt+b.cnt};
}
Info add(int k){
return (Info){mx+k,cnt};
}
}f[500005],pr[500005],sf[500005],p[500005];
void dfs1(int u,int fa){
f[u]=(Info){0,1};
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs1(v,u);f[u]=f[u]+f[v].add(1);
}
}
int tmp[500005];Info ans=(Info){0,1};
int cmp(Info x,Info y){
return x.mx>y.mx;
}
void dfs2(int u,int fa,Info out){
int siz=0;vector<int>tmp;vector<Info>nxt;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
siz++;
}
tmp.resize(siz+5);nxt.resize(siz+5);siz=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
tmp[++siz]=v;
}
pr[0]=(Info){0,0};
for(int i=1;i<=siz;i++){
pr[i]=pr[i-1]+f[tmp[i]].add(1);
}
sf[siz+1]=(Info){0,0};
for(int i=siz;i>=1;i--){
sf[i]=sf[i+1]+f[tmp[i]].add(1);
}
for(int i=1;i<=siz;i++){
nxt[i]=(out+pr[i-1]+sf[i+1]+(Info){0,1}).add(1);
}
int len=0;
for(int i=1;i<=siz;i++)p[++len]=f[tmp[i]].add(1);
p[++len]=out;sort(p+1,p+len+1,cmp);
if(len<3){
for(int i=1;i<=siz;i++)dfs2(tmp[i],u,nxt[i]);
return;
}
if(p[3].mx==p[1].mx){
ll sum=0,res=0;for(int i=1;i<=len;i++)if(p[i].mx==p[1].mx)res+=p[i].cnt*sum,sum+=p[i].cnt;
ans=ans+(Info){(p[2].mx+p[3].mx)*p[1].mx,res};
}
else if(p[2].mx==p[1].mx){
ll sum=0;for(int i=1;i<=len;i++)if(p[i].mx==p[3].mx)sum+=p[i].cnt;
ans=ans+(Info){(p[2].mx+p[3].mx)*p[1].mx,sum*(p[1].cnt+p[2].cnt)};
}
else if(p[2].mx==p[3].mx){
ll sum=0,res=0;for(int i=1;i<=len;i++)if(p[i].mx==p[2].mx)res+=p[i].cnt*sum,sum+=p[i].cnt;
ans=ans+(Info){(p[2].mx+p[3].mx)*p[1].mx,res};
}
else{
ll sum=0;for(int i=1;i<=len;i++)if(p[i].mx==p[3].mx)sum+=p[i].cnt;
ans=ans+(Info){(p[2].mx+p[3].mx)*p[1].mx,p[2].cnt*sum};
}
for(int i=1;i<=siz;i++){
dfs2(tmp[i],u,nxt[i]);
}
}
signed main(){
int n=read();for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
dfs1(1,0);dfs2(1,0,(Info){0,0});printf("%lld %lld\n",ans.mx,ans.cnt);
return 0;
}
D 【1109 B组】最后一道
发现删点的影响过大了,并不好直接维护,考虑建虚点。称原图中存在的点为黑点,在每条边上新建一个白点,那么两黑点有边的条件是它们通过一个白点相连。在删除一个黑点的时候,相当于把这个点的父亲白点和儿子白点合并成一个点。可以将答案三元组 \((a,b,c)\) 看成 \((a,b,c,x,y)\),表示 \(a,c\) 到 \(b\) 的白点是 \(x,y\),分情况维护答案:
-
当 \(x=y\) 时,记 \(f_u\) 表示白点 \(u\) 的儿子个数,答案为 \(\sum_u (f_u+1)f_u(f_u-1)\);
-
当 \(x\neq y\),且 \(x,y\) 都是 \(b\) 的儿子时,记 \(g_u\) 表示黑点 \(u\) 的二级儿子个数,答案是 \(\sum_ug_u^2-\sum_{v\in son(u)}f_v^2\);
-
当 \(x\neq y\),且 \(x,y\) 一个是 \(b\) 的儿子,一个是父亲时,记 \(h_u\) 表示白点 \(u\) 的三级儿子个数,考虑枚举 \(b\) 的父亲,答案是 \(\sum_u2f_uh_u\)。
注意到第二种情况的后面一项对于每个白点会恰好计算一次,于是答案可以写成 \((\sum_{u}(f_u+1)f_u(f_u-1)-f_u^2+2f_uh_u)+(\sum_{v}g_v^2)\),其中 \(u\) 是白点 \(v\) 是黑点。删点的时候并查集合并即可。有一个细节是可以选 \(n\) 为根,这样不用考虑根被删掉的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[800005];
int tot,head[400005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
void adde(int u,int v){
add(u,v),add(v,u);
}
int fa[400005];
int find(int x){
return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
int n,ans,f[400005],g[400005],h[400005],Fa[400005];
void dfs(int u,int p){
Fa[u]=p;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==p)continue;
dfs(v,u);
if(u>n)f[u]++,h[u]+=g[v];
else g[u]+=f[v];
}
}
int val(int i){
if(i==0)return 0;
if(i<=n)return g[i]*g[i];
else return f[i]*(f[i]+1)*(f[i]-1)-f[i]*f[i]+2*f[i]*h[i];
}
signed main(){
n=read();
for(int i=1;i<=2*n-1;i++)fa[i]=i;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),adde(u,i+n),adde(v,i+n);
dfs(n,0);
for(int i=1;i<=2*n-1;i++)ans+=val(i);
for(int u=1;u<=n;u++){
printf("%lld\n",ans);
int a=find(Fa[u]),b=Fa[a],c=find(Fa[b]);ans-=val(u);
ans-=val(a),ans-=val(b),ans-=val(c);
h[c]--;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==Fa[u])continue;
h[c]+=f[v];ans-=val(v);fa[v]=a;
}
g[b]--;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==Fa[u])continue;
g[b]+=f[v];
}
f[a]--,h[a]-=g[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==Fa[u])continue;
f[a]+=f[v],h[a]+=h[v];
}
ans+=val(a),ans+=val(b),ans+=val(c);
}
return 0;
}
C0382 【1110 B组】南京联测
A 【1110 B组】串
枚举一个 \(i\),考虑 \(S[1,i-1]=T'[1,i-1],S[i]>T'[i]\) 的答案,直接算。需要注意不能漏算把后缀全删完的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char s[200005],t[200005];set<int>st[35];
signed main(){
scanf("%s%s",s+1,t+1);
int n=strlen(s+1),m=strlen(t+1),ans=m;
for(int i=1;i<=m;i++)st[t[i]-'a'+1].insert(i);
for(int i=1,j=1,cnt=0;i<=n;i++){
int res=inf;
for(int k=1;k<s[i]-'a'+1;k++)if(!st[k].empty())res=min(res,*st[k].begin());
ans=min(ans,min(res-j+cnt,m-j+1+cnt));
while(j<=m&&s[i]!=t[j])st[t[j]-'a'+1].erase(j),j++,cnt++;
if(j>m)break;
st[t[j]-'a'+1].erase(j),j++;
}
printf("%lld\n",ans);
return 0;
}
B 【1110 B组】序列
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
char s[500005],t[500005];int vis[500005],p[500005];
signed main(){
scanf("%s%s",s+1,t+1);
int n=strlen(s+1),m=strlen(t+1),now=1;
for(int i=1;i<=n;i++)if(now<=m&&s[i]==t[now])p[now++]=i,vis[i]=1;
if(now!=m+1)return puts("NO"),0;
for(int i=n-1;i>=1;i--){
if(m&&p[m]==i)m--;
if(s[i]==s[i+1]||vis[i]||vis[i+1])continue;
if(!m)return puts("NO"),0;
if(s[p[m]]==s[i])vis[p[m]]=0,vis[i]=1,p[m]=i,m--;
else vis[p[m]]=0,vis[i+1]=1,p[m]=i+1,m--;
}
puts("YES");
for(int i=1;i<=n;i++)if(vis[i])printf("%lld ",i);
return 0;
}
/*
11101110111011101110111
00000
*/
C 【1110 B组】树
考虑 \(\sum\limits_{d|n}\varphi(d)=n\),所以原式等于 \(\sum\limits_{d=1}^n\varphi(d)\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}[d|\text{dist}(id,jd)]\)。枚举 \(d\),每次把涉及到的点拉出来建虚树跑点分治即可,复杂度 \(\mathcal{O}(n\ln n\log n)\),常数大,需要卡常,还不一定能卡过。一种更优雅的做法是启发式合并,复杂度不变,但常数小了不少。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
#define ULL unsigned long long
#define U128 __uint128_t
struct Barrett {
ULL d;
U128 m;
Barrett() {}
Barrett(ULL _d):d(_d),m(((U128)(1)<<64)/_d) {}
ULL reduce(ULL x) {
ULL w=(m*x)>>64;
w=x-w*d;
if(w>=d) return w-d;
return w;
}
} mod;
ULL operator%(ll x,Barrett &y) {return y.reduce(x);}
struct edge{
int v,nxt;
}e[600005];
int tot,head[300005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
void adde(int u,int v){
add(u,v),add(v,u);
}
int n,m,cur,dfn[300005],Log[300005],f[22][300005],dep[300005];
int cmp(int x,int y){
return dfn[x]<dfn[y];
}
int getmin(int x,int y){
return ((dfn[x]<dfn[y])?x:y);
}
void dfs(int u,int fa){
dfn[u]=++cur,f[0][cur]=fa;dep[u]=dep[fa]+1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);
}
}
int getlca(int x,int y){
if(x==y)return x;
if((x=dfn[x])>(y=dfn[y]))swap(x,y);
int o=31^__builtin_clz(y-x++);
return getmin(f[o][x],f[o][y-(1ll<<o)+1]);
}
int I,id[300005],rnk[300005],siz[300005],son[300005],B[300005];ll now,ans;
void init(int u,int fa){
id[u]=++I,rnk[I]=u;siz[u]=1,son[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
init(v,u);siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
void sol(int u,int fa,int tag){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
sol(v,u,0);
}
if(son[u])sol(son[u],u,1);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
for(int j=id[v];j<=id[v]+siz[v]-1;j++){
if(rnk[j]%mod==0)now+=B[(2*dep[u]%mod-dep[rnk[j]]%mod+m)%mod];
}
for(int j=id[v];j<=id[v]+siz[v]-1;j++){
if(rnk[j]%mod==0)B[dep[rnk[j]]%mod]++;
}
}
if(u%mod==0)now+=B[(2*dep[u]%mod-dep[u]%mod+m)%mod],B[dep[u]%mod]++;
if(!tag){
for(int i=id[u];i<=id[u]+siz[u]-1;i++){
if(rnk[i]%mod==0)B[dep[rnk[i]]%mod]--;
}
}
}
int T,a[300005],st[300005],phi[300005],p[300005],np[300005];
signed main(){
n=read();phi[1]=1;
for(int i=2;i<=n;i++){
if(!np[i])p[++T]=i,phi[i]=i-1;
for(int j=1;j<=T&&i*p[j]<=n;j++){
np[i*p[j]]=1;
if(i%p[j]==0){phi[i*p[j]]=phi[i]*p[j];break;}
else phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
for(int i=1,u,v;i<n;i++){
u=read(),v=read(),adde(u,v);
}
dfs(1,0);int o=31^__builtin_clz(n);
for(int j=1;j<=o;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
f[j][i]=getmin(f[j-1][i],f[j-1][i+(1<<(j-1))]);
}
}
for(m=2;m<=n;m++){
int cnt=0,top=0;mod=Barrett(m);
for(int i=m;i<=n;i+=m){
a[++cnt]=i,head[i]=0;
}
sort(a+1,a+cnt+1,cmp);
tot=0;head[1]=0;st[++top]=1;
for(int i=1;i<=cnt;i++){
if(a[i]==1)continue;
int lca=getlca(a[i],st[top]);
if(lca!=st[top]){
while(dfn[lca]<dfn[st[top-1]]){
adde(st[top],st[top-1]),top--;
}
if(dfn[lca]>dfn[st[top-1]]){
head[lca]=0;adde(st[top],lca);st[top]=lca;
}
else{
adde(st[top],lca);top--;
}
}
st[++top]=a[i];
}
for(int i=1;i<top;i++){
adde(st[i+1],st[i]);
}
now=I=0;init(1,0);sol(1,0,0);ans+=1ll*now*phi[m];
}
printf("%lld\n",ans*2ll+1ll*(n+1)*n/2+1ll*(n-1)*n);
return 0;
}
D 【1110 B组】积木游戏
C0384 【1113 B组】模拟测试
A 【1113 B组】卡门
P6370 [COCI2006-2007#6] KAMEN。
不会做绿题。
发现暴力遍历的时候很浪费,因为从同一列上丢下来的两个球有很长一段前缀是相同的。考虑记录每一列上丢个球下来的路径,然后每次暴力调整。为什么这样做是对的?发现一个点最多成为一次路径中的点,因为它被弹出的时候说明它被填了,所以复杂度是 \(\mathcal{O}(rc^2)\) 的。
点击查看代码
#include<bits/stdc++.h>
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,T,a[30005][35],c[35];pii q[35][30005];char s[30005][35];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++){
if(s[i][j]=='.')a[i][j]=0;
else a[i][j]=1;
}
}
for(int i=1;i<=m;i++){
q[i][++c[i]]=mk(0,i);
}
T=read();
while(T--){
int t=read();
while(1){
int x=q[t][c[t]].fi,y=q[t][c[t]].se;
if(a[x][y]){c[t]--;continue;}
if(x==n||a[x+1][y]==1)break;
if(a[x+1][y]==0){x++;q[t][++c[t]]=mk(x,y);continue;}
if(y-1>=1&&a[x][y-1]==0&&a[x+1][y-1]==0){x++,y--;q[t][++c[t]]=mk(x,y);continue;}
if(y+1<=m&&a[x][y+1]==0&&a[x+1][y+1]==0){x++,y++;q[t][++c[t]]=mk(x,y);continue;}
break;
}
a[q[t][c[t]].fi][q[t][c[t]].se]=2;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]==0)putchar('.');
else if(a[i][j]==1)putchar('X');
else putchar('O');
}
puts("");
}
return 0;
}
B 【1113 B组】商人
P7831 [CCO2021] Travelling Merchant。
不会做蓝题。
令 \(f_u\) 表示从 \(u\) 开始能无限走的最小初始资产,那么有转移 \(f_v=\min(\max(r_i,f_u-p))\)。发现图上有环不能直接转,考虑一个神奇的转移方式:
把所有边按 \(r\) 从大到小排序,初始时 \(f_i=\infty\),建反图。先跑 topsort,正常转移,然后枚举剩下的边中最大的一条 \((u,v,r,p)\)。此时通过这条边走一定能走到一个环,且路径上所有点的限制都不超过 \(r\),所以这个点的限制就是 \(r\), \(f_v\gets \min(f_v,p)\),然后这条边的转移就没用了,把这条边删掉即可,重复这个流程直到图为空即可求出答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,V=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,r,p,id;
bool operator <(const edge &b)const{
return r>b.r;
}
};
vector<edge>g[200005];
struct Edge{
int u,v,r,p,id;
bool operator <(const Edge &b)const{
return r>b.r;
}
}all[200005];
int deg[200005],f[200005],del[200005];
signed main(){
int n=read(),m=read();
for(int i=1;i<=n;i++)f[i]=inf;
for(int i=1,u,v,r,p;i<=m;i++){
u=read(),v=read(),r=read(),p=read();deg[u]++;
g[v].push_back((edge){u,r,p,i}),all[i]=(Edge){v,u,r,p,i};
}
queue<int>q;for(int i=1;i<=n;i++)if(!deg[i])q.push(i);
sort(all+1,all+m+1);
for(int t=1;t<=m;t++){
while(!q.empty()){
int u=q.front();q.pop();
for(auto x:g[u]){
int v=x.v,r=x.r,p=x.p,id=x.id;
if(del[id])continue;
del[id]=1,f[v]=min(f[v],max(r,f[u]-p));
if((--deg[v])==0)q.push(v);
}
}
int u=all[t].u,v=all[t].v,r=all[t].r,p=all[t].p,id=all[t].id;
if(del[id])continue;
f[v]=min(f[v],r);del[id]=1;
if((--deg[v])==0)q.push(v);
}
for(int i=1;i<=n;i++){
printf("%lld ",(f[i]>=V)?-1ll:f[i]);
}
return 0;
}
C 【1113 B组】自行车
P9466 [EGOI2023] Bikes vs Cars / 骑车与汽车(暂无数据)。
\(c_{i,j}=\max\limits_{path(i,j)}\{\min\limits_{b}\{w-b\}\},b_{i,j}=\max\limits_{path(i,j)}\{\min\limits_{b}\{b\}\}\)。令 \(a_{i,j}=w-c_{i,j}\),则 \(a_{i,j}=\min\limits_{path(i,j)}\{\max\limits_{b}\{b\}\}\)。把 \(a_{i,j},b_{i,j}\) 看成连接 \(i,j\) 的边,发现如果一个图满足条件,那么这个图的最小生成树也满足 \(a\) 的限制,最大生成树也满足 \(b\) 的限制。证明可以考虑反证法,以 \(a\) 的限制为例,因为我们是在原图中去掉了一些边,所以两点间的 \(a'_{i,j}\) 只会变大。当 \(a'_{i,j}>a_{i,j}\) 时,可以把 \(i\to j\) 路径上的最大边替换成 \(a_{i,j}\),这样就更小了。
所以我们求出最小/最大生成树,把它们的边拼在一起去重跑 floyd 检查。注意 \(a_{i,j}>b_{i,j}\) 的边不能用,因为此时在最小生成树上 \(b_{i,j}\) 会取代 \(a_{i,j}\) 的位置,会让答案出错,最大生成树同理。可能会有多棵生成树,但它们都是等价的,随便求一棵即可。复杂度 \(\mathcal{O}(n^3)\),瓶颈是 floyd。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
typedef pair<pii,int>piii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int u,v,w;
bool operator <(const edge &b)const{
if(w^b.w)return w<b.w;
if(u^b.u)return u<b.u;
return v<b.v;
}
}e[260005];
int fa[505];
int find(int x){
return ((fa[x]==x)?x:fa[x]=find(fa[x]));
}
int d[505][505],a[505][505],b[505][505];vector<piii>ans;
signed main(){
int n=read(),w=read(),m=0,cnt=0;
for(int j=2;j<=n;j++){
for(int i=1;i<j;i++){
a[i][j]=w-read();
}
}
for(int j=2;j<=n;j++){
for(int i=1;i<j;i++){
b[i][j]=read();
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(b[i][j]>=a[i][j])e[++m]=(edge){i,j,a[i][j]},e[++m]=(edge){i,j,b[i][j]};
}
}
cnt=0;for(int i=1;i<=n;i++)fa[i]=i;
sort(e+1,e+m+1);
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if((u=find(u))==(v=find(v)))continue;
fa[v]=u,cnt++,ans.push_back(mk(mk(e[i].u,e[i].v),w));
if(cnt==n-1)break;
}
cnt=0;for(int i=1;i<=n;i++)fa[i]=i;
reverse(e+1,e+m+1);
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if((u=find(u))==(v=find(v)))continue;
fa[v]=u,cnt++,ans.push_back(mk(mk(e[i].u,e[i].v),w));
if(cnt==n-1)break;
}
sort(ans.begin(),ans.end());
ans.erase(unique(ans.begin(),ans.end()),ans.end());
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)d[i][j]=0;
else d[i][j]=inf;
}
}
for(auto x:ans){
int u=x.fi.fi,v=x.fi.se,w=x.se;
d[u][v]=d[v][u]=min(d[u][v],w);
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=min(d[i][j],max(d[i][k],d[k][j]));
}
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(d[i][j]!=a[i][j])return puts("NO"),0;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)d[i][j]=0;
else d[i][j]=-inf;
}
}
for(auto x:ans){
int u=x.fi.fi,v=x.fi.se,w=x.se;
d[u][v]=d[v][u]=max(d[u][v],w);
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=max(d[i][j],min(d[i][k],d[k][j]));
}
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(d[i][j]!=b[i][j])return puts("NO"),0;
}
}
printf("%lld\n",(int)ans.size());
for(auto x:ans){
int u=x.fi.fi,v=x.fi.se,w=x.se;
printf("%lld %lld %lld\n",u-1,v-1,w);
}
return 0;
}
D 【1113 B组】记忆
逆天题,不会。
C0385 【1114 B组】模拟测试
简单场,但没调完,挂麻了。
A 【1114 B组】印章
直接枚举左上角那个点,暴力涂色,复杂度 \(\mathcal{O}(nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
pii st[1000005];char s[1005][1005],t[1005][1005];
signed main(){
int T=1;
while(T--){
int n=read(),m=read(),a=read(),b=read(),top=0,flag=0,tag=0;
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++){
if(s[i][j]=='x')flag=1;
}
}
for(int i=1;i<=a;i++){
scanf("%s",t[i]+1);
}
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++){
if(t[i][j]=='x')st[++top]=mk(i,j);
}
}
if(flag&&!top){puts("NIE");continue;}
if(!flag&&!top){puts("TAK");continue;}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='.')continue;
int dx=i-st[1].fi,dy=j-st[1].se;
for(int k=1;k<=top;k++){
int nx=st[k].fi+dx,ny=st[k].se+dy;
if(nx<1||nx>n||ny<1||ny>m||s[nx][ny]!='x'){puts("NIE");tag=1;break;}
s[nx][ny]='.';
}
if(tag)break;
}
if(tag)break;
}
if(!tag)puts("TAK");
}
return 0;
}
B 【1114 B组】Deadlock
一个大小为 \(siz\) 的连通块内能连 \(\dfrac{siz(siz-1)}{2}\) 条边,容易发现当 \(siz\bmod 4<2\) 时上式为偶数,否则为奇数。一个局面下能连的边数是 \(t=\sum\dfrac{siz(siz-1)}{2}-m\),当 \(t\) 为奇数时 Cypher 赢,否则 Deadlock 赢。发现能改变 \(t\) 奇偶性的唯一方法是把两个大小为奇数的连通块连起来,所以有一个朴素的 \(\mathcal{O}(n^2)\) 做法:定义 \(f_{0/1/2,0/1,0/1,i,j}\),状态表示当前 \(1,n\) 两个点所在连通块共有几个是奇数大小,当前是谁在操作,当前 \(t\) 的奇偶性,除去 \(1,n\) 两点所在连通块后奇数/偶数大小的连通块个数为 \(i,j\),然后这个人能不能赢。终止条件为总奇数大小连通块个数不足 2,或者 \(1,n\) 两点大小为奇数,其余没有奇数大小连通块。随便转移一下即可。优化就是显然第二、第三维可以压成一维,然后把 \(f\) 打表出来,发现这个东西很有规律,随便找一下即可。
这是一个题解做法:容易发现最终的局面必然是仅剩下 \(1\) 和 \(n\) 两个连通块,每个连通块都是一个完全图。设 \(1\) 所在的连通块大小为 \(x\),则共需要操作 \(\dfrac{n(n+1)}{2}-m-x(n-x)\) 次,若该数为奇数,则先手胜,否则后手胜。
如果 \(n\) 是奇数,则 \(x(n-x)\) 必为偶数,可以直接判断;否则 \(n\) 是偶数,分两类讨论:
-
初始时 \(1\) 所在连通块和 \(n\) 所在连通块大小奇偶性相同,此时有偶数个奇连通块,如果先手/后手相要连接其中一个改变局势,则对应的后手/先手可以连接另一个,所以此时以 \(x\) 为初始 \(1\) 所在的连通块大小计算即可;
-
初始时 \(1\) 所在连通块和 \(n\) 所在连通块大小奇偶性不同,此时有奇数个奇连通块,如果先手用 \(1\) 和 \(n\) 连通块中大小为偶数的那个连接了一个奇连通块,则变成了上面那个情况,故先手必胜。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int siz[100005],vis[100005],c[5];
void dfs(int u,int col){
if(vis[u])return;
siz[col]++,vis[u]=col;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
dfs(v,col);
}
}
void solve(){
int n=read(),m=read(),k=0;
tot=0;for(int i=1;i<=n;i++)head[i]=vis[i]=siz[i]=0;
for(int i=0;i<4;i++)c[i]=0;
for(int i=1,u,v;i<=m;i++){
u=read(),v=read(),add(u,v),add(v,u);
}
int fst=m%2;
for(int i=1;i<=n;i++){
if(vis[i])continue;
siz[++k]=0,dfs(i,k),siz[k]%=4;
if(siz[k]>1)fst=(fst+1)%2;
if(vis[1]!=k&&vis[n]!=k)c[siz[k]%2]++;
}
if(vis[1]==vis[n])return puts("Deadlock"),void();
int i=(siz[vis[1]]%2)+(siz[vis[n]]%2),j=fst,a=c[1],ans=0;
if(i==0&&j==0)ans=(a%4==2||a%4==3);
if(i==0&&j==1)ans=(a%4==0||a%4==1);
if(i==0&&j==2)ans=1;
if(i==1&&j==0)ans=(a%4==1||a%4==2||a%4==3);
if(i==1&&j==1)ans=(a%4==0||a%4==1||a%4==3);
if(i==1&&j==2)ans=1;
if(i==2&&j==0)ans=(a%4==1||a%4==2);
if(i==2&&j==1)ans=(a%4==0||a%4==3);
if(i==2&&j==2)ans=1;
if(ans)puts("Cypher");
else puts("Deadlock");
}
signed main(){
int T=read();
while(T--){
solve();
}
return 0;
}
C 【1114 B组】二叉树
其他操作平凡,稍微特殊的是往左和往右的操作。容易发现向左相当于找到最后一次走右儿子的操作,然后把它改成走左儿子,它后面的走左儿子操作改成走右儿子,往右类似。这个东西可以线段树 \(\mathcal{O}(n\log n)\) 维护。算出来值之后,先让两点走到同一层,然后枚举它们往上走的层数,计算即可。容易发现答案不超过 \(2D\),所以不需要写高精。
实际上,上面的线段树完全没有必要。考虑维护颜色段,每次只会对最后一段进行反转,直接维护就是 \(\mathcal{O}(n)\) 的了。赛时写的是二分+线段树的不动脑 \(n\log^2 n\) 做法。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,lim=2e5;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int s,rev;
}c[800005];
void pushup(int p){
c[p].s=c[ls].s+c[rs].s;
}
void pushdown(int l,int r,int p){
if(!c[p].rev)return;
int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
c[ls].rev^=c[p].rev,c[rs].rev^=c[p].rev;
c[ls].s=ln-c[ls].s,c[rs].s=rn-c[rs].s;
c[p].rev=0;
}
void build(int l,int r,int p){
c[p].rev=0;
if(l==r){
c[p].s=0;
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(p);
}
void add(int l,int r,int p,int L,int R){
if(L>R)return;
if(L<=l&&r<=R){
c[p].rev^=1,c[p].s=r-l+1-c[p].s;
return;
}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)add(lson,L,R);
if(R>mid)add(rson,L,R);
pushup(p);
}
void upd(int l,int r,int p,int x,int k){
if(l==r){
c[p].s=k;
return;
}
int mid=(l+r)>>1;pushdown(l,r,p);
if(x<=mid)upd(lson,x,k);
else upd(rson,x,k);
pushup(p);
}
int ask(int l,int r,int p,int L,int R){
if(L>R)return 0;
if(L<=l&&r<=R){
return c[p].s;
}
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res=(res+ask(lson,L,R));
if(R>mid)res=(res+ask(rson,L,R));
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
char s[100005],t[100005];
int top,vs[100005],vt[100005],st[100005],tmp[100005];
signed main(){
scanf("%s%s",s+1,t+1);
int n=strlen(s+1),m=strlen(t+1);
Tr.build(1,n,1);
for(int i=1;i<=n;i++){
if(s[i]=='L'){
int l=1,r=top,res=top;
while(l<=r){
int mid=(l+r)>>1;
if(Tr.ask(1,n,1,mid,top)!=0)res=mid,l=mid+1;
else r=mid-1;
}
Tr.add(1,n,1,res,top);
}
else if(s[i]=='R'){
int l=1,r=top,res=top;
while(l<=r){
int mid=(l+r)>>1;
if(Tr.ask(1,n,1,mid,top)!=top-mid+1)res=mid,l=mid+1;
else r=mid-1;
}
Tr.add(1,n,1,res,top);
}
else if(s[i]=='U')Tr.upd(1,n,1,top,0),top--;
else if(s[i]=='1')top++,Tr.upd(1,n,1,top,0);
else top++,Tr.upd(1,n,1,top,1);
}
vs[top]++;
for(int i=1;i<=top;i++)if(Tr.ask(1,n,1,i,i))vs[top-i]++;
top=0;Tr.build(1,m,1);
for(int i=1;i<=m;i++){
if(t[i]=='L'){
int l=1,r=top,res=top;
while(l<=r){
int mid=(l+r)>>1;
if(Tr.ask(1,m,1,mid,top)!=0)res=mid,l=mid+1;
else r=mid-1;
}
Tr.add(1,m,1,res,top);
}
else if(t[i]=='R'){
int l=1,r=top,res=top;
while(l<=r){
int mid=(l+r)>>1;
if(Tr.ask(1,m,1,mid,top)!=top-mid+1)res=mid,l=mid+1;
else r=mid-1;
}
Tr.add(1,m,1,res,top);
}
else if(t[i]=='U')Tr.upd(1,m,1,top,0),top--;
else if(t[i]=='1')top++,Tr.upd(1,m,1,top,0);
else top++,Tr.upd(1,m,1,top,1);
}
vt[top]++;
for(int i=1;i<=top;i++)if(Tr.ask(1,m,1,i,i))vt[top-i]++;
int ans=inf,res=0,ps=0,pt=0;
for(int i=n;i>=0;i--)if(vs[i]!=0){ans+=i+1,ps=i;break;}
for(int i=m;i>=0;i--)if(vt[i]!=0){ans+=i+1,pt=i;break;}
if(ps>pt)swap(ps,pt),swap(vs,vt);
if(ps!=pt){
for(int i=pt,j=ps;i>=pt-ps;i--,j--)tmp[j]=vt[i];
for(int i=ps;i>=0;i--)vt[i]=tmp[i];
res+=pt-ps,pt=ps;
}
int now=0;ans=min(ans,2*(ps+1));
for(int i=ps;i>=0;i--){
now=now*2+(vs[i]-vt[i]);ans=min(ans,2*i+abs(now));
if(now>lim)break;
}
printf("%lld\n",ans+res);
return 0;
}
D 【1114 B组】麻将机
复杂的 flow,等会再写。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18,N=62;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int id(char ch){
if('0'<=ch&&ch<='9')return ch-'0'+1;
if('a'<=ch&&ch<='z')return ch-'a'+11;
return ch-'A'+37;
}
struct edge{
int v,w,nxt;
}e[100005];
int tot=1,head[205],now[205];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
void adde(int u,int v,int w){
add(u,v,w),add(v,u,0);
}
int dep[205],S,T;
int bfs(){
for(int i=1;i<=T;i++)dep[i]=0;
dep[S]=1;queue<int>q;q.push(S);now[S]=head[S];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
if(dep[e[i].v])continue;
if(e[i].w)now[e[i].v]=head[e[i].v],dep[e[i].v]=dep[u]+1,q.push(e[i].v);
}
}
return dep[T];
}
int dinic(int u,int flow){
if(u==T)return flow;
int rest=0;
for(int i=now[u];i&&flow;i=e[i].nxt){
now[u]=i;
if(e[i].w==0||dep[e[i].v]!=dep[u]+1)continue;
int k=dinic(e[i].v,min(e[i].w,flow));
if(k==0)dep[e[i].v]=0;
e[i].w-=k,e[i^1].w+=k;flow-=k,rest+=k;
}
return rest;
}
int cur,rc,top,st[105],dfn[105],low[105],in[105],a[105],col[105],val[105],p[105],siz[105],deg[105];
vector<int>g[105];
void tarjan(int u){
dfn[u]=low[u]=++cur;st[++top]=u;in[u]=1;
for(auto v:g[u]){
if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
else if(in[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;rc++;
do{
v=st[top--],in[v]=0;
col[v]=rc,val[rc]|=a[v],siz[rc]++;
p[rc]=v;
}while(u!=v);
}
}
char s[1005],t[1005];int d[105][105];
void solve(){
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
if(i!=j)d[i][j]=0;
else d[i][j]=1;
}
}
scanf("%s",s+1);int len=strlen(s+1),m=read();
for(int i=1;i<=len;i++)a[id(s[i])]=1;
vector<pii>alledge;
for(int i=1;i<=m;i++){
scanf("%s",t+1);d[id(t[1])][id(t[2])]=1;
alledge.push_back(mk(id(t[1]),id(t[2]))),deg[id(t[1])]++;
if(a[id(t[1])]&&a[id(t[2])])g[id(t[1])].push_back(id(t[2]));
}
for(int k=1;k<=N;k++){
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
d[i][j]|=d[i][k]&d[k][j];
}
}
}
for(int i=1;i<=N;i++)if(!dfn[i]&&a[i])tarjan(i);
S=N*2+1,T=N*2+2;
for(int i=1;i<=rc;i++)adde(S,p[i],1);
for(int i=1;i<=N;i++)if(!a[i])adde(i+N,T,1);
for(int i=1;i<=rc;i++)adde(p[i]+N,T,1);
for(int i=1;i<=rc;i++){
for(int j=1;j<=N;j++){
if(!d[p[i]][j]||col[j]==i)continue;
adde(p[i],j+N,1);
}
}
for(int i=1;i<=rc;i++){
if(siz[i]==1&&!deg[p[i]])adde(p[i],p[i]+N,1);
}
int ans=0;
for(int i=1;i<=N;i++)if(a[i])ans++;
ans-=rc;
while(bfs())ans+=dinic(S,inf);
printf("%lld\n",ans);
tot=1;for(int i=1;i<=T;i++)head[i]=now[i]=0;
for(int i=1;i<=N;i++)g[i].clear();
for(int i=1;i<=N;i++)a[i]=col[i]=dfn[i]=low[i]=in[i]=deg[i]=0;
for(int i=1;i<=rc;i++)val[i]=p[i]=siz[i]=0;
rc=top=cur=0;
}
signed main(){
int T=read();
while(T--){
solve();
}
return 0;
}
C0389 【1116 B组】模拟测试
A 【1116 B组】组织
从下到上贪心即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
namespace IO{
const int sz=1<<22;
char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
inline char gc(){
return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
}
template<class T> void gi(T& x){
x=0; char c=gc();
for(;c<'0'||c>'9';c=gc());
for(;c>='0'&&c<='9';c=gc())
x=x*10+(c-'0');
}
inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
template<class T> void pi(T x,char c='\n'){
if(x<0) pc('-'),x*=-1;
if(x==0) pc('0'); int t=0;
for(;x;x/=10) p[++t]=x%10+'0';
for(;t;--t) pc(p[t]); pc(c);
}
struct F{~F(){flush();}}f;
}
using IO::gi;
using IO::pi;
struct edge{
int v,nxt;
}e[2000005];
int tot,head[1000005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,cnt,ans,a[1000005];
void dfs(int u,int fa){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
if(a[u]&&a[v])a[u]=a[v]=0,ans++;
}
}
signed main(){
gi(n);
for(int i=1;i<=n;i++)gi(a[i]);
for(int i=1,u,v;i<n;i++){
gi(u),gi(v),add(u,v),add(v,u);
}
dfs(1,0);
for(int i=1;i<=n;i++)cnt+=a[i];
if(cnt%2==1)return puts("UNO"),0;
pi(ans+cnt);
return 0;
}
B 【1116 B组】神眷
首先划分成若干段可以合并成两段,记这两段的分割点为 \(p\)。然后对于每一位,要么这 \(n\) 个数都是 \(0\),要么记这一位为 \(1\) 的最小/最大的位置为 \(i,j\),需要满足 \(p\in[i,j)\),那么合法条件就是对所有区间的交不为空。
不妨枚举交集 \([l,r)\),记所有区间的左右端点分别是 \(L_i,R_i\),那么需要满足 \(\max\{L_i\}=l,\min\{R_i\}=r\)。发现这个限制很难做,不妨先令 \(L_i\le l,R_i\ge r\),记 \(f(l,r)=(1+\sum\limits_{1\le i\le l}\sum\limits_{r\le j\le n}2^{j-i-1})^k-1\),简单容斥得到 \([l,r)\) 对应的答案是 \(f(l,r)-f(l-1,r)-f(l,r+1)+f(l-1,r+1)\),求和即可,记得加上没有区间的一种方案数。
显然 \(f\) 可以通过 \(\mathcal{O}(n)\) 预处理 \(\mathcal{O}(\log k)\) 计算,直接枚举 \(l,r\) 是 \(\mathcal{O}(n^2\log k)\) 的,考虑优化。对 \(f(l,r)\) 的系数进行打表后发现只会用到 \(f(l,l+1)\) 和 \(f(l,l+2)\),且系数分别为 \(1,-1\),于是可以 \(\mathcal{O}(n\log k)\) 计算答案。
实际上上述结论可以证明。不妨先对 \(f(l,l+1)\) 求和,然后减去多算的部分。这个时候对于所有的 \(l,r\),他会被计算 \(r-l+1\) 次,且 \(f(l,l+2)\) 会把所有的 \(l,r\) 计算 \(r-l\) 次,相减刚好得到正确答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353,i2=(mod+1)/2,V=1e5+2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;b=(b%mod+mod)%mod;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
int n,k,ans,pw[200005],ipw[200005];
int f(int l,int r){
if(l<1||r>n)return 0;
int res=1,tmp1=(ipw[1]-ipw[l+1]+mod)%mod,tmp2=(pw[n+1]-pw[r]+mod)%mod;
res=(res+tmp1*tmp2%mod)%mod;
res=(qpow(res,k)-1+mod)%mod;
return res%mod;
}
signed main(){
n=read(),k=read(),ans=1;
pw[0]=1;for(int i=1;i<=V;i++)pw[i]=pw[i-1]*2ll%mod;
ipw[0]=1;for(int i=1;i<=V;i++)ipw[i]=ipw[i-1]*i2%mod;
for(int l=1;l<=n;l++){
if(l+1<=n)ans=(ans+f(l,l+1))%mod;
if(l+2<=n)ans=(ans-f(l,l+2)+mod)%mod;
}
printf("%lld\n",ans*qpow(ipw[n],k)%mod);
return 0;
}
C 【1115 B组】尘埃
发现当 \(a_i\) 的异或和不为 \(0\) 时,每一段的异或和已经确定,可以直接 dp。否则,我们可以枚举每一段的异或和,然后跑上面的 dp。因为这个值一定是一段前缀的异或和,所以最坏复杂度是 \(\mathcal{O}(n^2\log n)\) 的。
考虑优化。对于上述 dp,假设异或和是 \(k\),发现每个分割点处的前缀异或和一定是 \(k,0,k,0\ldots\) 这样交替的,所以只有这些位置可能成为分割点,把这些位置拉出来跑 dp 即可。进一步地,\(0\) 其实是没用的,我们只需要知道两个 \(k\) 之间有几个 \(0\),可以预处理。复杂度优化到 \(\mathcal{O}(n\log n)\),瓶颈在于离散化。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
namespace IO{
const int sz=1<<22;
char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
inline char gc(){
return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
}
template<class T> void gi(T& x){
x=0; char c=gc();
for(;c<'0'||c>'9';c=gc());
for(;c>='0'&&c<='9';c=gc())
x=x*10+(c-'0');
}
inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
template<class T> void pi(T x,char c='\n'){
if(x<0) pc('-'),x*=-1;
if(x==0) pc('0'); int t=0;
for(;x;x/=10) p[++t]=x%10+'0';
for(;t;--t) pc(p[t]); pc(c);
}
struct F{~F(){flush();}}f;
}
using IO::gi;
using IO::pi;
int n,ans,a[1000005],s[1000005],f[1000005];map<int,int>mp;
void add(int &x,int y){
x+=y;if(x>=mod)x-=mod;
}
void solve(int val){
mp.clear();f[0]=1,mp[s[0]]=1;
for(int i=1;i<=n;i++){
f[i]=0;
if(mp.count(s[i]^val))add(f[i],mp[s[i]^val]);
if(f[i])add(mp[s[i]],f[i]);
}
add(ans,f[n]);
}
int m,z[1000005],B[1000005];vector<int>v[1000005];
void calc(int val){
int vz=1,vg=1;
for(int i=1;i<(int)v[val].size();i++){
add(vz,1ll*(z[v[val][i]]-z[v[val][i-1]])*vg%mod),add(vg,vz);
}
add(ans,vg);
}
signed main(){
gi(n);
for(int i=1;i<=n;i++)gi(a[i]),s[i]=s[i-1]^a[i];
for(int i=0;i<=n;i++)B[++m]=s[i];
for(int i=1;i<=n;i++)z[i]=z[i-1]+(s[i]==0);
if(s[n]!=0){solve(s[n]),pi(ans);return 0;}
sort(B+1,B+m+1);m=unique(B+1,B+m+1)-B-1;solve(0);
for(int i=0;i<=n;i++)s[i]=lower_bound(B+1,B+m+1,s[i])-B;
for(int i=1;i<=n;i++)v[s[i]].push_back(i);
for(int i=1;i<=m;i++)if(B[i]!=0)calc(i);
pi(ans);
return 0;
}
D 【1116 B组】宿命
由于翻转区间 \([l,r]\) 需要满足 \(a_l=a_r\),所以 \(a_i=a_{i+1}\) 的位置数量不会变化,这个“最大化 \(a_i=a_{i+1}\) 个数”的条件其实是假的。
然后有个结论。对于原来的序列 \(a\) 和随便一个序列 \(b\),如果 \(a_1=b_1\),\(a_n=b_n\),且 \(\forall i,\{a_i,a_{i+1}\}\) 这个可重集和 \(\forall i,\{b_i,b_{i+1}\}\) 这个可重集是相同的,那么 \(a\) 一定能操作到 \(b\)。

根据这个结论,连 \(a_i\leftrightarrow a_{i+1}\) 的边,跑一个 \(a_1\) 开始 \(a_n\) 结束的字典序最小的欧拉路径即可。容易发现从 \(a_1\) 开始则必定以 \(a_n\) 结尾,直接做就行了。复杂度 \(\mathcal{O}(n)\),略微卡空间。
点击查看代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#define fi first
#define se second
#define mk make_pair
using namespace std;
typedef pair<int,int>pii;
struct xorShift128Plus {
unsigned long long k1, k2;
unsigned long long gen() {
register unsigned long long k3 = k1, k4 = k2;
k1 = k4;
k3 ^= k3 << 23;
k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
return k2 + k4;
}
int gen(int w) {
return gen()%w;
}
}rnd;
const int S=5000005;
int n,a[S];
int tot,ans[S],vis[S],used[S];
vector<pii>g[S];
void dfs(int u){
for(int i=vis[u];i<(int)g[u].size();i=vis[u]){
vis[u]=i+1;
if(!used[g[u][i].se])used[g[u][i].se]=1,dfs(g[u][i].fi);
}
ans[++tot]=u;
}
int main()
{
int t;
scanf("%d%d",&n,&t);
if(t==0) for(int i=1;i<=n;i++) scanf("%d",&a[i]);
else
{
int ra;
scanf("%d%llu%llu",&ra,&rnd.k1,&rnd.k2);
for(int i=1;i<=n;i++) a[i]=rnd.gen(ra)+1;
}
for(int i=1;i<n;i++){
g[a[i]].push_back(mk(a[i+1],i));
g[a[i+1]].push_back(mk(a[i],i));
}
for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
dfs(a[1]);reverse(ans+1,ans+tot+1);
if(t==0)
{
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
printf("\n");
}
else
{
int bse=1919839,p=1000000007;
int mul=1,res=0;
for(int i=1;i<=n;i++,mul=1ll*mul*bse%p) res=(res+1ll*ans[i]*mul%p)%p;
printf("%d\n",res);
}
return 0;
}
C0390 【1117 B组】模拟测试
C0393 【1122 A组】模拟测试
已经是老东西了。
A 【1122 A组】路径
简单题,倍增搞一下即可。复杂度 \(\mathcal{O}((n+q)\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
struct Info{
int mx,mn,s;
Info operator +(const Info &b)const{
return (Info){max(mx,b.mx),min(mn,b.mn),max({s,b.s,mx-b.mn})};
}
}f[22][200005];
int dep[200005],pa[22][200005],a[200005];
void dfs(int u,int fa){
pa[0][u]=fa,f[0][u]=(Info){a[u],a[u],0};dep[u]=dep[fa]+1;
for(int i=1;i<=20;i++)pa[i][u]=pa[i-1][pa[i-1][u]];
for(int i=1;i<=20;i++)f[i][u]=f[i-1][pa[i-1][u]]+f[i-1][u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs(v,u);
}
}
int solve(int u,int v){
Info res=(Info){-inf,inf,0};
for(int i=20;i>=0;i--){
if(dep[u]-dep[v]>=(1<<i))res=f[i][u]+res,u=pa[i][u];
}
return (f[0][u]+res).s;
}
signed main(){
int n=read(),q=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
for(int i=1;i<=n;i++)a[i]=read();
dfs(1,0);
while(q--){
int u=read(),v=read();
printf("%d\n",solve(u,v));
}
return 0;
}
B 【1122 A组】迷失
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
struct edge{
int v,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,top,rc,dfn[100005],low[100005],st[100005],in[100005],bel[100005];
void tarjan(int u){
dfn[u]=low[u]=++cur;st[++top]=u,in[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
else if(in[v])low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;rc++;
do{
v=st[top--],in[v]=0;
bel[v]=rc;
}while(u!=v);
}
}
int vis[100005],dep[100005];vector<int>o[100005];
void calc(int u,int d){
vis[u]=1,dep[u]=d;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!vis[v])calc(v,d+1);
else if(bel[v]==bel[u])o[bel[u]].push_back(abs(dep[u]-dep[v]+1));
}
}
int gcd(int x,int y){
return ((!y)?x:gcd(y,x%y));
}
int lcm(int x,int y){
if(!x||!y)return x+y;
return x/gcd(x,y)*y;
}
int n,m,t,fac[100005];
struct mat{
bitset<205>a[205];
mat(){for(int i=0;i<n;i++)a[i].reset();}
void init(){
for(int i=0;i<n;i++)a[i][i]=1;
}
mat operator *(const mat &b)const{
mat c;
for(int i=0;i<n;i++){
for(int k=0;k<n;k++){
if(a[i][k])c.a[i]|=b.a[k];
}
}
return c;
}
};
mat qpow(mat b,int p){
mat res;res.init();
for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
return res;
}
int eu[200005],ev[200005];
int check(mat a,mat b){
for(int i=0;i<n;i++){
int cc=(a.a[i]&b.a[i]).count(),ca=a.a[i].count(),cb=b.a[i].count();
if(cc!=ca||cc!=cb)return 0;
}
return 1;
}
mat pw[55];
signed main(){
n=read(),m=read(),t=read();
for(int i=1,u,v;i<=m;i++){
u=read(),v=read(),add(u,v),eu[i]=u-1,ev[i]=v-1;
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++)if(!vis[i])calc(i,1);
for(int i=1;i<=rc;i++){
int tmp=0;
for(auto x:o[i])tmp=gcd(tmp,x);
for(int j=2;j*j<=tmp;j++){
int cnt=0;
while(tmp%j==0)tmp/=j,cnt++;
fac[j]=max(fac[j],cnt);
}
if(tmp>1)fac[tmp]=max(fac[tmp],1ll);
}
int ans=1;
for(int i=1;i<=n;i++){
if(t==0)ans=ans*qpow(i,fac[i])%mod;
else{while(fac[i]--)ans*=i;}
}
if(t==0)return printf("%lld\n",ans%mod),0;
mat base;
for(int i=1;i<=m;i++)base.a[eu[i]][ev[i]]=1;
mat cyc=qpow(base,ans);pw[0]=base;
for(int i=1;i<=30;i++)pw[i]=pw[i-1]*pw[i-1];
int res=0;mat tmp;tmp.init();
for(int i=30;i>=0;i--)if(!check(tmp*pw[i],tmp*pw[i]*cyc))res+=(1ll<<i),tmp=tmp*pw[i];
printf("%lld %lld\n",(res+1)%mod,ans%mod);
return 0;
}
C 【1122 A组】树
C0395 【1124 A组】模拟测试
被吊打了,破防了。
A 【1124 A组】传奇特级超空间
容易发现一个 \(f_{n,m}\) 的递推式,即 \(f_{n,m}=f_{n-1,m}+f_{n-1,m-1}\)。解释一下。考虑在 \(f_{n-1,m}\) 的基础上加一个 \(m-1\) 维的平面,思考这个平面会增加多少个空间。容易发现变化量就是这个 \(m-1\) 的平面被剩下的平面分割成的段数,而这相当于在这个 \(m-1\) 维空间上去割,这就是 \(f_{n-1,m-1}\)。终止条件是当 \(n=0\) 或 \(m=0\) 时,\(f_{n,m}=1\)。
画一下图,\(f_{n,m}\) 相当于从 \((n,m)\) 出发,只能往右上或正上走,走到第 \(0\) 行或第 \(0\) 列的方案数。因为走到第 \(0\) 列后唯一方案是继续向上走到 \((0,0)\),所以可以只统计走到第 \(0\) 行的方案。由于每一步都会向上,所以一共走 \(n\) 步。不妨枚举最后往左走了 \(i\) 步,即可得到 \(f_{n,m}=\sum\limits_{i=0}^m\dbinom{n}{i}\)。
最终答案是 \(\sum\limits_{i=0}^nf_{i,m}=\sum\limits_{i=0}^n\sum\limits_{j=0}^m\dbinom{i}{j}=\sum\limits_{j=0}^m\sum\limits_{i=0}^n\dbinom{i}{j}\)。需要对一列组合数求和,考虑组合数经典结论,上式等于 \(\sum\limits_{j=0}^m\dbinom{n+1}{j+1}\)。令 \(n\gets n+1,m\gets m+1\),思考如何求 \(\sum\limits_{j=0}^m\dbinom{n}{j}-1\)。
考虑 Lucas 定理,记 \(g_{n,m}=\sum\limits_{i=0}^m\dbinom{n}{i}\),那么 \(g_{n,m}=g_{n\bmod p,p-1}\times g_{\lfloor\frac{n}{p}\rfloor,\lfloor\frac{m}{p}\rfloor-1}+\dbinom{\lfloor\frac{n}{p}\rfloor}{\lfloor\frac{m}{p}\rfloor}g_{n\bmod p,m\bmod p}\)。递推即可,复杂度似乎是 \(\mathcal{O}(p\log_p^2\min(n,m))\),精细实现应该可以做到一个 \(\log\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
#define ULL unsigned long long
#define U128 __uint128_t
struct Barrett {
ULL d;
U128 m;
Barrett() {}
Barrett(ULL _d):d(_d),m(((U128)(1)<<64)/_d) {}
ULL reduce(ULL x) {
ULL w=(m*x)>>64;
w=x-w*d;
if(w>=d) return w-d;
return w;
}
} mod;
ULL operator%(int x,Barrett &y) {return y.reduce(x);}
int jc[20000005],iv[20000005],ij[20000005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int Lucas(int n,int m,int P){
if(n<P&&m<P)return C(n,m);
return Lucas(n/P,m/P,P)*C(n%mod,m%mod)%mod;
}
int solve(int n,int m,int P){
if(n<0||m<0)return 0;
m=min(m,n);
if(n<P&&m<P){
int res=0;
for(int i=0;i<=m;i++)res=(res+C(n,i))%mod;
return res;
}
int t1=solve(n/P,m/P-1,P);
int t2=solve(n%mod,P-1,P);
return (t1*t2%mod+Lucas(n/P,m/P,P)*solve(n%mod,m%mod,P)%mod)%mod;
}
signed main(){
int n=read()+1,m=read()+1,P=read();mod=Barrett(P);
jc[0]=1;for(int i=1;i<P;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<P;i++)iv[i]=P-(P/i)*iv[P%i]%mod;
ij[0]=1;for(int i=1;i<P;i++)ij[i]=ij[i-1]*iv[i]%mod;
printf("%lld\n",(solve(n,m,P)-1+P)%mod);
return 0;
}
B 【1124 A组】传奇特级法术树
暴力地,我们考虑对 \(n^2\) 个二元组进行二维数点。发现这样有很多点对是无用的,比如有一个 \((a,b),(c,d)\),且 \(\text{lca}(a,b)=\text{lca}(c,d)\),\(a\le c\le d\le b\),那么 \((a,b)\) 就是无用的。
思考一对点 \((a,b)\) 有用的必要条件。考虑在 \(\text{lca}(a,b)=x\) 处统计这样的点对。首先 \(a,b\) 一定在 \(x\) 的不同子树内,随便固定一个点 \(a\),记 \(S\) 表示 \(x\) 子树内除了 \(a\) 子树内的点集,容易发现 \(b\) 必须是 \(S\) 中 \(a\) 的前驱或者后缀。发现如果我们进行一个 dsu on tree,在轻子树内枚举 \(a\),找 \(b\),那么只会找到 \(\mathcal{O}(n\log n)\) 个点对。然后询问离线跑二维数点即可。复杂度 \(\mathcal{O}(n\log^2 n+m\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,m,T,son[100005],siz[100005],cur,dfn[100005],rnk[100005];ll d[100005],b[100005];
void init(int u,int fa){
siz[u]=1;son[u]=0;dfn[u]=++cur,rnk[cur]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;if(v==fa)continue;
d[v]=d[u]+w;init(v,u);siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
struct Node{
int x,y,lca;
};
set<int>s;vector<Node>p[100005];
int pre(int x){
if(s.empty())return inf;
auto it=s.lower_bound(x);
if(it==s.begin())return inf;
return *prev(it);
}
int suf(int x){
if(s.empty())return -inf;
auto it=s.upper_bound(x);
if(it==s.end())return -inf;
return *it;
}
void calc(int u,int fa,int tag){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
calc(v,u,0);
}
if(son[u])calc(son[u],u,1);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==son[u])continue;
for(int j=dfn[v];j<=dfn[v]+siz[v]-1;j++){
if(pre(rnk[j])<=rnk[j])p[rnk[j]].push_back((Node){pre(rnk[j]),rnk[j],u});
if(suf(rnk[j])>=rnk[j])p[suf(rnk[j])].push_back((Node){rnk[j],suf(rnk[j]),u});
}
for(int j=dfn[v];j<=dfn[v]+siz[v]-1;j++){
s.insert(rnk[j]);
}
}
if(pre(u)<=u)p[u].push_back((Node){pre(u),u,u});
if(suf(u)>=u)p[suf(u)].push_back((Node){u,suf(u),u});
s.insert(u);
if(!tag){
for(int i=dfn[u];i<=dfn[u]+siz[u]-1;i++){
s.erase(rnk[i]);
}
}
}
int ql[500005],qr[500005],lst[100005],ans[500005];vector<int>v[100005];
struct BIT{
int c[100005];
void add(int x,int v){
for(;x<=n;x+=x&-x)c[x]+=v;
}
int ask(int x){
int res=0;
for(;x;x-=x&-x)res+=c[x];
return res;
}
int ask(int l,int r){
if(l>r)return 0;
return ask(r)-ask(l-1);
}
}Tr;
signed main(){
n=read(),m=read();
for(int i=1,u,v,w;i<n;i++){
u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
}
for(int i=1;i<=m;i++)ql[i]=read(),qr[i]=read();
for(int i=1;i<=m;i++)v[qr[i]].push_back(i);
init(1,0);for(int i=1;i<=n;i++)b[++T]=d[i];
sort(b+1,b+T+1);T=unique(b+1,b+T+1)-b-1;
for(int i=1;i<=n;i++)d[i]=lower_bound(b+1,b+T+1,d[i])-b;
calc(1,0,0);
for(int i=1;i<=n;i++)p[i].push_back((Node){i,i,i});
for(int i=1;i<=n;i++){
for(auto x:p[i]){
if(lst[d[x.lca]])Tr.add(lst[d[x.lca]],-1);
lst[d[x.lca]]=max(lst[d[x.lca]],x.x);
if(lst[d[x.lca]])Tr.add(lst[d[x.lca]],1);
}
for(auto x:v[i]){
ans[x]=Tr.ask(ql[x],qr[x]);
}
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
C 【1124 A组】传奇特级字符串
C0401 【1129 A组】模拟测试
A 【1119 A组】分身
二分答案 \(v\)。能从 \((t_i,x_i)\) 走到 \((t_j,x_j)\) 的条件是 \(\left|\dfrac{x_j-x_i}{t_j-t_i}\right|\le v\),即 \(-v\le \dfrac{x_j-x_i}{t_j-t_i}\le v\),化一下就是 \(vt_j+x_j\ge vt_i+x_i\) 且 \(vt_j-x_j\ge vt_i-x_i\)。记 \(a_i=vt_i+x_i\),\(b_i=vt_i-x_i\),就是问最小链覆盖是否不超过 \(m+1\)。把 \((a_i,b_i)\) 当成坐标系上的点,因为最小链覆盖等于最长反链,相当于从左上角一路往下走,求最长不降子序列即可。
记得判断能从初始状态走到每个点。
点击查看代码
#include<bits/stdc++.h>
#define db double
using namespace std;
const db inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,tot,t[100005],x[100005];db b[100005],f[100005];
struct Node{
db x,y;
}a[100005];
int cmp(Node x,Node y){
return x.x<y.x;
}
int check(db mid){
for(int i=1;i<=n;i++)if(mid*t[i]<abs(x[i]))return 0;
for(int i=1;i<=n;i++)a[i].x=mid*t[i]+x[i],a[i].y=-mid*t[i]+x[i];
sort(a+1,a+n+1,cmp);int ans=0;f[0]=-inf;
for(int i=1;i<=n;i++){
if(f[ans]<a[i].y)f[++ans]=a[i].y;
else f[lower_bound(f+1,f+ans+1,a[i].y)-f]=a[i].y;
}
return (ans<=m+1);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)t[i]=read(),x[i]=read();
db l=0,r=2e6,res=r;
for(int i=1;i<=40;i++){
db mid=(l+r)/2.0;
if(check(mid))res=mid,r=mid;
else l=mid;
}
printf("%.6lf\n",res);
return 0;
}
B 【1129 A组】递归
观察大样例可以发现如果把每个序列分成 \(s_i=(A_{i,1})(A_{i,2})\ldots (A_{i,m_i})\) 的形式,记 \(c_{i,T}=\sum\limits_{j=1}^{m_i}[A_{i,j}=T]\),容易发现后手必胜当且仅当 \(\forall T,\oplus_{i=1}^kc_{i,T}=0\),否则先手必胜。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int inf=1e18,N=5e5;
const ull base=13331;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int cnt,len,sum[500005];ull pw[500005],h[500005];char s[500005];
ull ask(int l,int r){
return h[r]-h[l-1]*pw[r-l+1];
}
map<ull,int>mp;
void solve(){
for(int i=1;i<=len;i++){
if(s[i]=='(')sum[i]=sum[i-1]+1;
else sum[i]=sum[i-1]-1;
}
for(int i=1;i<=len;i++){
h[i]=h[i-1]*base+s[i];
}
map<ull,int>tmp;
for(int i=1,lst=1;i<=len;i++){
if(sum[i]==0){
tmp[ask(lst,i)]++;
lst=i+1;
}
}
for(auto x:tmp){
if(mp[x.first])cnt--;
mp[x.first]^=x.second;
if(mp[x.first])cnt++;
}
}
void print(){
if(cnt)puts("1");
else puts("0");
}
signed main(){
pw[0]=1;for(int i=1;i<=N;i++)pw[i]=pw[i-1]*base;
int n=read();
for(int i=1;i<=n;i++){
scanf("%s",s+1),len=strlen(s+1),solve(),print();
}
return 0;
}
C 【1129 A组】序列
考虑每个位置维护前缀 max,问题变成在一段时间内对后缀取 max,询问一段前缀的历史和。如果加上时间轴一维画成图大概是这样:

注意到取 max 操作只在一段时间内有影响,如果直接扫描线无法支持删除。但是如果我们把矩阵转过来,扫 \(x\) 这一维,就不需要删除了。需要维护区间取 max,求区间历史和,复杂度 \(\mathcal{O}(m\log m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct segtree{
#define ls (p<<1)
#define rs (p<<1|1)
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int cnt,mn,se,tag,ad,dl,a,ta;
}c[4000005];
void pushup(int p){
c[p].mn=min(c[ls].mn,c[rs].mn);c[p].cnt=0,c[p].se=inf;
if(c[ls].mn==c[p].mn)c[p].cnt+=c[ls].cnt,c[p].se=min(c[p].se,c[ls].se);
else c[p].se=min(c[p].se,c[ls].mn);
if(c[rs].mn==c[p].mn)c[p].cnt+=c[rs].cnt,c[p].se=min(c[p].se,c[rs].se);
else c[p].se=min(c[p].se,c[rs].mn);
c[p].ad=c[ls].ad+c[rs].ad;
c[p].dl=c[ls].dl+c[rs].dl;
}
void pushdown(int l,int r,int p){
int mid=(l+r)>>1,ln=mid-l+1,rn=r-mid;
if(c[ls].mn<c[p].tag)c[ls].ad+=c[ls].cnt*c[p].a,c[ls].dl+=c[ls].cnt*c[p].ta,c[ls].a+=c[p].a,c[ls].ta+=c[p].ta;
if(c[rs].mn<c[p].tag)c[rs].ad+=c[rs].cnt*c[p].a,c[rs].dl+=c[rs].cnt*c[p].ta,c[rs].a+=c[p].a,c[rs].ta+=c[p].ta;
if(c[ls].mn<c[p].tag)c[ls].tag=c[p].tag,c[ls].mn=c[p].tag;
if(c[rs].mn<c[p].tag)c[rs].tag=c[p].tag,c[rs].mn=c[p].tag;
c[p].a=c[p].ta=0,c[p].tag=-inf;
}
void build(int l,int r,int p){
c[p].a=c[p].ta=0,c[p].tag=-inf;
if(l==r){
c[p].mn=c[p].ad=c[p].dl=0,c[p].se=inf,c[p].cnt=1;
return;
}
int mid=(l+r)>>1;
build(lson);build(rson);
pushup(p);
}
void add(int l,int r,int p,int L,int R,int v,int t){
if(c[p].mn>=v)return;
if(L<=l&&r<=R){
if(c[p].mn<v&&v<c[p].se){
c[p].a+=(v-c[p].mn),c[p].ta+=t*(v-c[p].mn);
c[p].ad+=c[p].cnt*(v-c[p].mn),c[p].dl+=c[p].cnt*t*(v-c[p].mn);
c[p].tag=v,c[p].mn=v;
return;
}
}
if(l==r)return;
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)add(lson,L,R,v,t);
if(R>mid)add(rson,L,R,v,t);
pushup(p);
}
int ask(int l,int r,int p,int L,int R,int t){
if(L<=l&&r<=R){
return c[p].ad*t-c[p].dl;
}
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res+=ask(lson,L,R,t);
if(R>mid)res+=ask(rson,L,R,t);
return res;
}
#undef lson
#undef rson
#undef ls
#undef rs
}Tr;
int qx[1000005],qy[1000005],ql[1000005],qr[1000005],lst[1000005],ans[1000005];vector<int>q1[1000005],q2[1000005];
signed main(){
int n=read(),m=read();
for(int i=1;i<=m;i++)qx[i]=read(),qy[i]=read(),q2[qx[i]].push_back(i);
for(int i=1;i<=n;i++)lst[i]=m+1;
for(int i=m;i>=1;i--)ql[i]=i,qr[i]=lst[qx[i]]-1,lst[qx[i]]=i,q1[qx[i]].push_back(i);
Tr.build(1,m,1);
for(int i=1;i<=n;i++){
for(auto x:q1[i])Tr.add(1,m,1,ql[x],qr[x],qy[x],i-1);
for(auto x:q2[i])ans[x]=Tr.ask(1,m,1,1,x,i);
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号