10月CWOI杂题

C0324 【1002 B组】模拟测试

家里做题没检查,挂了一堆分。

A 【1002 B组】分组

把这些人按经验从小到大排序,经验相同的按 231 排,此时一组的组长一定在他的组员后面。有朴素 dp \(f_{i,j,k}\) 表示前 \(i\) 个人,有 \(j\) 个完整的组,\(k\) 个组缺组长的最小薪水。

这样看似是 \(\mathcal{O}(nk^2)\) 的,但 \(2k>n\) 时一定无解,故 \(k\le \sqrt{nk}\),复杂度是 \(\mathcal{O}(V\sqrt{V})\) 的,其中 \(V=\max\{nk\}=10^5\)。记得 dp 的时候也要判无解。

点击查看代码
#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 Node{
	int w,s,p;
}a[100005];
int cmp(Node x,Node y){
	if(x.w^y.w)return x.w<y.w;
	return x.p<y.p;
}
int f[2][505][505];
signed main(){
	int n=read(),m=read();
	if(2*m>n)return puts("-1"),0;
	for(int i=1;i<=n;i++){
		a[i].w=read(),a[i].s=read(),a[i].p=(read()+1)%3+1;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=0;i<2;i++){
		for(int j=0;j<=m;j++){
			for(int k=0;k<=m;k++){
				f[i][j][k]=inf;
			}
		}
	}
	f[0][0][0]=0;
	for(int i=1;i<=n;i++){
		int o=i&1ll;
		for(int j=0;j<=m;j++){
			for(int k=0;k<=m;k++){
				f[o][j][k]=f[o^1ll][j][k];
				if(a[i].p==3){
					if(j-1>=0&&k+1<=m)f[o][j][k]=min(f[o][j][k],f[o^1ll][j-1][k+1]+a[i].s);
				}
				else if(a[i].p==1){
					if(k-1>=0)f[o][j][k]=min(f[o][j][k],f[o^1ll][j][k-1]+a[i].s);
				}
				else{
					if(j-1>=0&&k+1<=m)f[o][j][k]=min(f[o][j][k],f[o^1ll][j-1][k+1]+a[i].s);
					if(k-1>=0)f[o][j][k]=min(f[o][j][k],f[o^1ll][j][k-1]+a[i].s);
				}
			}
		}
	}
	printf("%lld\n",((f[n&1ll][m][0]>=inf)?-1:f[n&1ll][m][0]));
	return 0;
} 

B 【1002 B组】字符串

首先 T 一定是 S 的子序列。那么我们贪心的找到字典序最小的那个子序列,记这个下标序列为 \(p\)。容易发现“\(\forall i\in[1,n),a_i\neq a_{i+1}\)\(i\)\(i\)\(i+1\) 必须至少有一个在答案序列中”是答案序列优秀的充要条件。考虑对 \(p\) 进行一些调整来得到答案序列。具体地,我们从后往前枚举 \(a_i\neq a_{i+1}\) 的位置,如果两个都不在 \(p\) 中,我们就把 \(p_j<i\) 的最大的 \(p_j\) 改成 \(i\)\(i+1\)。因为这是一个 01 序列,所以当 \(p_1<i\) 时这一定是可以做到的;同时,由于 \(p\) 字典序的最小性,如果此时 \(p_1>i\),显然一定无解。复杂度 \(\mathcal{O}(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;
}
int a[300005],b[300005],vis[300005],pos[300005];
signed main(){
	int n=read(),m=read(),now=1;
	for(int i=1;i<=n;i++)scanf("%1lld",&a[i]);
	for(int i=1;i<=m;i++)scanf("%1lld",&b[i]);
	for(int i=1;i<=n;i++)if(now<=m&&a[i]==b[now])pos[now++]=i,vis[i]=1;
	if(now!=m+1)return puts("-1"),0;
	for(int i=n-1;i>=1;i--){
		if(m&&pos[m]==i)m--;
		if(a[i]==a[i+1]||vis[i]||vis[i+1])continue;
		if(!m)return puts("-1"),0;
		if(a[pos[m]]==a[i])vis[pos[m]]=0,vis[i]=1,pos[m]=i,m--;
		else vis[pos[m]]=0,vis[i+1]=1,pos[m]=i+1,m--;
	}
	for(int i=1;i<=n;i++)if(vis[i])printf("%lld ",i);
	return 0;
}

C 【1002 B组】折纸

考虑一个一维的问题。首先这个折纸可以等效为裁纸,每次把小的那部分砍掉。然后一个区间 \([l,r]\) 能被折出来等价于 \([1,l]\)\([r,n]\) 能被折出来,因为这是独立的。这里只考虑怎么判断 \([1,i]\) 能否被折出来,另一个类似。令 \(t_i\) 表示回文中心在 \(i\)\(i+1\) 之间的最长回文串长度,不难发现 \([1,i]\) 能被折出来当且仅当存在 \(j\in[i-l_i,i)\),且 \(j\) 能被折出来,画图不难理解。然后一维你就做到 \(\mathcal{O}(n)\) 了,不用 manacher 就是 \(\mathcal{O}(n\log n)\)

拓展到二维后,你发现这两维之间是独立的,于是哈希转成一维,两个答案乘起来即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int inf=1e18;
const ull base=131; 
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,f[1000005],g[1000005];char t[1000005],s[1000005];
ull v[1000005],pw[1000005],pre[1000005],suf[1000005];
int id(int x,int y){
	return (x-1)*m+y;
}
ull ask(int o,int l,int r){
	if(o==0)return pre[r]-pre[l-1]*pw[r-l+1];
	else return suf[l]-suf[r+1]*pw[r-l+1];
}
int get(int pos,int len){
	int l=1,r=min(pos-1,len-pos+1),res=-1;
	while(l<=r){
		int mid=(l+r)/2;
		if(ask(0,pos-mid,pos-1)==ask(1,pos,pos+mid-1))res=mid,l=mid+1;
		else r=mid-1;
	}
	return res;
}
int sol1(){
	int pos,res=0,sum=0;for(int i=1;i<=m;i++)v[i]=0,f[i]=0,g[i]=0;
	for(int j=1;j<=m;j++)for(int i=1;i<=n;i++)v[j]=v[j]*base+(t[id(i,j)]-'a'+1);
	pw[0]=1;for(int i=1;i<=m;i++)pw[i]=pw[i-1]*base;
	pre[0]=0;for(int i=1;i<=m;i++)pre[i]=pre[i-1]*base+v[i];
	suf[m+1]=0;for(int i=m;i>=1;i--)suf[i]=suf[i+1]*base+v[i];
	f[1]=1,pos=1;for(int i=2;i<=m;i++)if(pos>=i-get(i,m))f[i]=1,pos=i;
	g[m]=1,pos=m;for(int i=m-1;i>=1;i--)if(pos<=i+get(i+1,m))g[i]=1,pos=i;
	for(int i=1;i<=m;i++)sum+=f[i],res+=g[i]*sum;
	return res;
}
int sol2(){
	int pos,res=0,sum=0;for(int i=1;i<=n;i++)v[i]=0,f[i]=0,g[i]=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)v[i]=v[i]*base+(t[id(i,j)]-'a'+1);
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*base;
	pre[0]=0;for(int i=1;i<=n;i++)pre[i]=pre[i-1]*base+v[i];
	suf[n+1]=0;for(int i=n;i>=1;i--)suf[i]=suf[i+1]*base+v[i];
	f[1]=1,pos=1;for(int i=2;i<=n;i++)if(pos>=i-get(i,n))f[i]=1,pos=i;
	g[n]=1,pos=n;for(int i=n-1;i>=1;i--)if(pos<=i+get(i+1,n))g[i]=1,pos=i;
	for(int i=1;i<=n;i++)sum+=f[i],res+=g[i]*sum;
	return res;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++)t[id(i,j)]=s[j];
	}
	printf("%lld\n",sol1()*sol2());
	return 0;
} 

D 【1002 B组】集合

在 Fusion tree 的基础上加了一个全局异或。这个全局异或其实就是交换左右儿子,直接做是 \(\mathcal{O}(n\log n)\) 的。考虑打个 tag,需要的时候再下传,复杂度就对了。最好在全局加一得时候特判一下 \(a_i=2^{30}-1\) 的情况。

点击查看代码
#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;
}
int T,root,son[50000005][2],c[50000005],tag[50000005];
void pushup(int p){
	c[p]=c[son[p][0]]+c[son[p][1]];
}
void pushdown(int p,int k){
	if(!tag[p])return;
	if((tag[p]>>k)&1ll)swap(son[p][0],son[p][1]);
	if(son[p][0])tag[son[p][0]]^=tag[p];
	if(son[p][1])tag[son[p][1]]^=tag[p];
	tag[p]=0;
}
void ins(int &p,int k,int v,int x){
	if(!p)p=++T;
	if(k>29){c[p]+=x;return;}
	pushdown(p,k);
	ins(son[p][(v>>k)&1ll],k+1,v,x);
	pushup(p);
}
void del(int &p,int k,int v,int x){
	if(!p)p=++T;
	if(k>29){c[p]-=x;return;}
	pushdown(p,k);
	del(son[p][(v>>k)&1ll],k+1,v,x);
	pushup(p);
}
int ask(int p,int k,int v){
	if(!p)return 0;
	if(k>29)return c[p];
	pushdown(p,k);
	return ask(son[p][(v>>k)&1ll],k+1,v);
}
void addall(int p,int k){
	if(!p)return;
	pushdown(p,k);
	swap(son[p][0],son[p][1]);
	addall(son[p][0],k+1);
	pushup(p);
}
void xorall(int p,int k,int v){
	if(!p)return;
	tag[p]^=v;
}
vector<int>vec;
void solve(int p,int k,int v){
	if(!c[p])return;
	if(k>29){
		int cnt=c[p];
		while(cnt--)vec.push_back(v);
		return;
	}
	pushdown(p,k); 
	if(c[son[p][0]])solve(son[p][0],k+1,v);
	if(c[son[p][1]])solve(son[p][1],k+1,v+(1ll<<k));
}
void print(){
	vec.clear();solve(root,0,0);sort(vec.begin(),vec.end());
	for(auto x:vec)printf("%d ",x);
	puts("");
}
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++)ins(root,0,read(),1);
	while(m--){
		int op=read(),x;
		if(op==1)x=read(),ins(root,0,x,1);
		else if(op==2)x=read(),del(root,0,x,1);
		else if(op==3){
			int cnt=ask(root,0,(1ll<<30)-1);
			del(root,0,(1ll<<30)-1,cnt);
			addall(root,0);
			ins(root,0,0,cnt);
		}
		else x=read(),xorall(root,0,x);
	}
	print();
	return 0;
}

C0327 【1003 B组】模拟测试

A 【1003 B组】运输

\(s=\sum\limits_{i=1}^na_i\),要让方差最小,就是让 \(s\bmod n\) 个人最终为 \(\lfloor\dfrac{s}{n}\rfloor+1\),其他人最终为 \(\lfloor\dfrac{s}{n}\rfloor\)。定义 \(f_{i,j}\) 表示在以 \(i\) 为根的子树内有 \(j\) 个点最终为 \(\lfloor\dfrac{s}{n}\rfloor+1\) 的最小代价。考虑最终的代价可以拆成每条边的贡献,比如边 \((u,v,w)\),设 \(u\) 的子树内原来有 \(x\) 个松果,最终有 \(y\) 个松果,那么这条边的贡献就是 \(|a-b|w\)。然后这个东西就是一个树形背包,复杂度 \(\mathcal{O}(n^2)\)

点击查看代码
#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,w,nxt;
}e[10005];
int tot,head[5005]; 
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,s,a[5005],val[5005],siz[5005],sum[5005],f[5005][5005],g[5005];
void dfs(int u,int fa){
	siz[u]=1,sum[u]=a[u];f[u][0]=f[u][1]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;if(v==fa)continue;
		val[v]=w;dfs(v,u);siz[u]+=siz[v];sum[u]+=sum[v];
		for(int j=0;j<=min(siz[u],s%n);j++)g[j]=inf;
		for(int j=0;j<=min(siz[u],s%n);j++){
			for(int k=0;k<=min(siz[v],j);k++){
				g[j]=min(g[j],f[u][j-k]+f[v][k]);
			}
		}	
		for(int j=0;j<=min(siz[u],s%n);j++)f[u][j]=g[j];		
	}
	for(int j=0;j<=min(siz[u],s%n);j++){
		f[u][j]+=abs(siz[u]*(s/n)+j-sum[u])*val[u];
	}
} 
void solve(){
	n=read(),s=0;for(int i=1;i<=n;i++)a[i]=read(),s+=a[i];
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=n;i++)for(int j=0;j<=s%n;j++)f[i][j]=inf;
	for(int i=1,u,v,w;i<n;i++)u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
	dfs(1,0);printf("%lld\n",f[1][s%n]);
}
signed main(){
	int T=read();
	while(T--){
		solve();
	} 
	return 0;
} 

B 【1003 B组】或

首先 \(l=r\) 时答案为 1。考虑从高到低找到 \(l,r\) 第一个不同的位置,前面相同的可以抛掉。此时,\(l\) 最高位 \(k\) 为 0 而 \(r\) 最高位为 1。容易发现此时我们能利用的数是 \([1,2^k)\) 的一段后缀和 \([2^k,2^{k+1})\) 的一段前缀。我们把能组合出的数分第 \(k\) 位为 0/1 讨论。

当第 \(k\) 位为 0 时,由于 \(a|b\ge \max\{a,b\}\),所以我们能组合出的数就是 \([l,2^k)\),用他们自己组合。

当第 \(k\) 位为 1 时,由于这是一段前缀,我们找到此时 \(r\) 去掉第 \(k\) 位后的最高位 \(p\)(没有即为 -1),那么我们就可以利用 \(2^k,2^k+2^0,2^k+2^1\ldots 2^k+2^p\)。显然这些数可以组合出 \([2^k,2^k+2^{p+1})\) 间的所有数,那么我们把它和 \([l+2^k,2^k+2^k)\) 取个并即可。

复杂度 \(\mathcal{O}(\log 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;
}
int highbit(int x){
	for(int i=60;i>=0;i--)if((x>>i)&1ll)return i;
	return -1;
}
signed main(){
	int l=read(),r=read();
	if(l==r)return puts("1"),0;
	for(int i=60;i>=0;i--){
		if(((l>>i)&1ll)==((r>>i)&1ll)){
			if(((l>>i)&1ll)==1&&((r>>i)&1ll)==1)l-=(1ll<<i),r-=(1ll<<i);
			continue;
		}
		int ans=0,pos=(1ll<<i),cnt=highbit(r-pos);
		ans+=pos-l;
		if((1ll<<(cnt+1))-1>=l)ans+=(1ll<<i);
		else ans+=(1ll<<(cnt+1))+pos-l;			
		printf("%lld\n",ans);
		return 0;
	}
	puts("-1");
	return 0;
}

C 【1003 B组】数

一眼莫反,第二眼发现看错了。

考虑枚举 \(\gcd(x_i)=d\),令 \(f(d)=\prod\limits_{d|x_i}\text{lcm}(x_i)\)\(g(d)=\prod\limits_{\gcd(x_i)=d}\text{lcm}(x_i)\),那么答案就是 \(\prod\limits_{d=1}^m g(d)^d\)。先假设我们能快速求出 \(f(d)\),怎么求 \(g(d)\)?不妨从大到小枚举 \(d\),假设当前已经知道 \([d+1,m]\)\(g\),那么有递推式 \(g(d)=\dfrac{f(d)}{\prod\limits_{k=2}^{kd\le m}g(kd)}\)\(g\) 可以直接求逆元。那么我们就在 \(\mathcal{O}(m\ln m\log mod)\) 的时间内求出了 \(g\)

考虑怎么求 \(f\),这个看上去就很典。把 \(d\) 提出来,令 \(lim=\lfloor\dfrac{m}{d}\rfloor\),现在要求 \(\prod\limits_{x_i\le lim}\text{lcm}(x_i)\)。把 \(\text{lcm}\) 拆成每个质因子单独计算。当 \(\text{lcm}(x_i)\)\(p\) 这一项的指数为 \(c\) 时,需要满足 \(\exists i\in[1,n],p^c\mid x_i\land p^{c+1}\nmid x_i\)。这个玩意可以容斥一下,然后做完了。复杂度 \(\mathcal{O}(m\ln m\log n)\),很松,实际完全达不到。可以整除分块优化,但我没写。

点击查看代码
#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 P){
	int res=1;
	for(;p;p>>=1,b=b*b%P)if(p&1ll)res=res*b%P;
	return res;
}
int calc(int lim,int n,int v,int P){
	return (qpow(lim,n,P)-qpow(lim-lim/v,n,P)+P)%P;
}
int tot,np[2000005],p[2000005],f[2000005],g[2000005],ig[2000005];
signed main(){
	int n=read(),m=read(),ans=1;
	for(int i=2;i<=m;i++){
		if(!np[i])p[++tot]=i;
		for(int j=1;j<=tot&&i*p[j]<=m;j++){
			np[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
	for(int d=1,lim;d<=m;d++){
		lim=m/d,f[d]=qpow(d,qpow(lim,n,mod-1),mod);
		for(int i=1;i<=tot;i++){
			if(p[i]>lim)break;
			for(int j=1,v=p[i];v<=lim;j++,v*=p[i]){
				f[d]=f[d]*qpow(v,(calc(lim,n,v,mod-1)-calc(lim,n,v*p[i],mod-1)+(mod-1))%(mod-1),mod)%mod;
			}
		}
	}
	for(int i=m;i>=1;i--){
		g[i]=f[i];
		for(int j=i+i;j<=m;j+=i)g[i]=g[i]*ig[j]%mod;
		ig[i]=qpow(g[i],mod-2,mod);
	}
	for(int i=1;i<=m;i++)ans=ans*qpow(g[i],i,mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

D 【1003 B组】图

神仙题。

CF814E 的加强版。下面是一个时间复杂度 \(\mathcal{O}(n^3)\),空间复杂度 \(\mathcal{O}(n^2)\) 的做法。

根据题意,容易发现可以把原图分层,每层编号连续且到 1 的最短路长度相同。同时还可以发现除第一层外,每个点会有正好 1 个上一层的点连向自己,然后层数相同的点可以随便连边。因为 \([2,n]\) 每个点都一定向前一层连一条边,我们可以把 \([2,n]\) 的点的度数全部减去 1,现在我们认为他们的度数是 1 或 2。

容易想到 dp。令 \(f(i,j)\) 表示考虑前 \(i\) 个点,第 \(i\) 个点是它所在层的结尾,且这一层向下一层连了 \(j\) 条边的方案数。转移就是 \(f(i,j)g(c_1,c_2,k)\to f(i+j,k)\),其中 \(c_1,c_2\) 表示 \((i,i+j]\) 里有多少个度数为 1/2 的点,\(g(c_1,c_2,k)\) 表示有 \(c_1\) 个度数为 1 的点和 \(c_2\) 个度数为 2 的点,且在内部连边后剩下 \(k\) 条边连向下一层的方案数。现在问题便为如何求出 \(g(c_1,c_2,k)\)

有一个时空复杂度均为 \(\mathcal{O}(n^3)\) 的做法,大概是根据每个点的连边情况分类讨论然后直接转移,不过这不是重点。思考怎么从组合的方式入手,将空间复杂度优化到 \(\mathcal{O}(n^2)\)

先看 \(c_2=0,k=0\) 的情况。令 \(u(c_1)\) 表示此时的方案数,那么有 \(u(c_1)=(c_1-1)\times u(c_1-2)\),因为你相当于在给第一个点配对,同时我们也可以发现 \(u(i)\) 只有在 \(i\) 是偶数时才有定义,反之你配不完。

然后看 \(k=0\)。一个自然的想法是把一个度数为 2 的点 \(i\) 拆成两个点 \(i_1\)\(i_2\),然后随便连边,转化成上一种情况。但是这样有三个问题:

  • 会出现自环:\((i_1\leftrightarrow i_2)\)

  • 会出现重边:\((i_1\leftrightarrow j_1),(i_2\leftrightarrow j_2)\)\((i_1\leftrightarrow j_2),(i_2\leftrightarrow j_1)\)

  • 系数不清楚:对于点 \(a,b\)\((i_1\leftrightarrow a),(i_2\leftrightarrow b)\)\((i_1\leftrightarrow b),(i_2\leftrightarrow a)\) 其实是同一种方案,需要乘 \(\dfrac{1}{2}\),但我们不知道要乘多少个。

对于第三个问题,你会发现不论 \(a,b\) 是在这一层还是下一层,我们都会重复计算两种方案,所以全部都乘 \(\dfrac{1}{2^{c_2}}\) 即可。

然后看前两个问题。考虑容斥。设自环有 \(i\) 个,重边有 \(j\) 个,那么我们枚举自环和重边的总数 \(r=i+j\),然后枚举 \(i,j\)。因为自环只涉及一个点而重边涉及两点,此时未被钦定的点有 \(p=c_1+2(c_2-(2i+j))\)(拆点后)。那么

\[g(c_1,c_2,0)=\dfrac{1}{2^{c_2}}\sum_{r=0}^{c_2}(-1)^r\sum_{\substack{i+j=r\\p=c_1+2(c_2-(2i+j))}}\dbinom{c_2}{2i+j}\dbinom{2i+j}{j}u(p)u(2i)2^i \]

然后我们加上 \(k\)。这很简单,有序选 \(k\) 个点即可。所以

\[g(c_1,c_2,k)=\dfrac{k!}{2^{c_2}}\sum_{r=0}^{c_2}(-1)^r\sum_{\substack{i+j=r\\p=c_1+2(c_2-(2i+j))}}\dbinom{c_2}{2i+j}\dbinom{p}{k}\dbinom{2i+j}{j}u(p-k)u(2i)2^i \]

注意上述式子都需要满足 \((p-k)\equiv 0\pmod 2\)

然后这个式子很丑陋,可以把它化的更好看一点。我们先枚举 \(x=2i+j\) 再枚举 \(i\),然后 \(p=c_1+2(c_2-x)\),得到新形式

\[g(c_1,c_2,k)=\dfrac{k!}{2^{c_2}}\sum_{x=0}^{c_2}\dbinom{c_2}{x}u(p-k)\sum_{2i+j=x}(-1)^{i+j}\dbinom{x}{j}u(2i)2^i \]

然后发现后面那坨只与 \(x\) 有关,我们可以 \(\mathcal{O}(n^2)\) 预处理,记作 \(h(x)\)。把这个式子带回 \(f\) 的转移式

\[f(i,j)\dfrac{k!}{2^{c_2}}\sum_{x=0}^{c_2}\dbinom{c_2}{x}u(p-k)h(x)\to f(i+j,k) \]

发现前面那坨跟 \(i,j\) 没有关系,于是可以分步转移,即

\[g(i+j,p=c_1+2(c_2-x))\gets f(i,j)\sum_{x=0}^{c_2}\dbinom{c_2}{x}h(x) \]

\[f(i,k)\gets\dfrac{k!}{2^{c_2}}\dbinom{p}{k}u(p-k)g(i,p) \]

做完了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9,mod=1e9+7,i2=(mod+1)/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 jc[2005],iv[2005],ij[2005]; 
int C(int n,int m){
	if(n<0||m<0||n-m<0)return 0;
	return 1ll*jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int d[2005],s1[2005],s2[2005],pw2[2005],ipw2[2005],u[2005],f[2005][2005],g[2005][2005],h[2005];
signed main(){
	int n=read();for(int i=1;i<=n;i++)d[i]=read();
	for(int i=1;i<=n;i++)s1[i]=s1[i-1]+(d[i]==2);
	for(int i=1;i<=n;i++)s2[i]=s2[i-1]+(d[i]==3);
	jc[0]=1;for(int i=1;i<=2*n+1;i++)jc[i]=1ll*jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=2*n+1;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=2*n+1;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod;
	pw2[0]=1;for(int i=1;i<=2*n+1;i++)pw2[i]=1ll*pw2[i-1]*2%mod;
	ipw2[0]=1;for(int i=1;i<=2*n+1;i++)ipw2[i]=1ll*ipw2[i-1]*i2%mod;
	u[0]=1;for(int i=2;i<=2*n+1;i+=2)u[i]=1ll*u[i-2]*(i-1)%mod;
	for(int x=0;x<=n;x++){
		for(int i=0;i<=x/2;i++){
			int j=x-2*i;
			if((i+j)&1ll)h[x]=(h[x]-1ll*C(x,j)*u[2*i]%mod*pw2[i]%mod+mod)%mod;
			else h[x]=(h[x]+1ll*C(x,j)*u[2*i]%mod*pw2[i]%mod)%mod;
		}
	}
	f[1][d[1]]=1;
	for(int i=1;i<=n;i++){
		for(int p=0;p<=2*i+1;p++){
			if(!g[i][p])continue;
			for(int k=(p&1ll);k<=p&&i+k<=n;k+=2){
				f[i][k]=(f[i][k]+1ll*g[i][p]*C(p,k)%mod*u[p-k]%mod*jc[k]%mod)%mod;
			}
		}
		for(int j=1;i+j<=n;j++){
			if(!f[i][j])continue;
			int c1=s1[i+j]-s1[i],c2=s2[i+j]-s2[i];
			for(int x=0;x<=c2;x++){
				int p=c1+2*(c2-x);
				g[i+j][p]=(g[i+j][p]+1ll*f[i][j]*C(c2,x)%mod*ipw2[c2]%mod*h[x]%mod)%mod;
			}
		}
	}
	printf("%d\n",f[n][0]);
	return 0;
}

C0329 【1005 B组】模拟测试

依托史。

A 【1005 B组】返乡

\(k=\lfloor\dfrac{3n}{2}\rfloor\),答案就是所有满足 \(a,b,c\in[0,n]\)\(a+b+c=k\)\((a,b,c)\)

证明就是显然偏序关系构成一张有向无环图,然后答案就是在反图上选最长的一条链,根据 Dilworth 定理有最长反链等于最小链覆盖,然后这就是最小链覆盖?

算了,这好像是 Sperner 定理,反正是结论。

点击查看代码
#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 n=read(),m=3*n/2,cnt=0;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			int k=m-i-j;
			if(k>=0&&k<=n)cnt++;
		}
	}
	printf("%lld\n",cnt);
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			int k=m-i-j;
			if(k>=0&&k<=n)printf("%lld %lld %lld\n",i,j,k);
		}
	}
	return 0;
}

B 【1005 B组】连接

赛时写挂了,-60pts。sad.

容易发现我们截的区间至少有一个端点在每段材料的边界处。更进一步的,如果只有一个端点在边界上,当且仅当我们在凑 \(L\) 或者 \(R\) 的质量。以上结论显然,证明可以考虑调整方案。

只有一个端点的部分可以直接扫一遍 \(\mathcal{O}(n)\) 做;两个端点的可以二分答案,然后问题变成每个右端点 \(i\) 对应的左端点只能在一个区间 \([l_i,r_i]\) 里选,且 \(l_i,r_i\) 单调不减,问你能不能选一个区间使得区间和 \(\ge 0\)。预处理之后每次二分时做一个滑动窗口即可,复杂度 \(\mathcal{O}(n\log V)\)

这里写的是 ST 表,多带个 \(\log n\),但也能过。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define db double
using namespace std;
const db inf=1e9,eps=1e-6;
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,L,R,pos,a[300005],b[300005],s[300005],t[300005],q[300005],lp[300005],rp[300005],Log[300005];db ans,v[300005];
db f[25][300005];
db ask(int l,int r){
	if(l>r)return inf;
	int o=Log[r-l+1];
	return min(f[o][l],f[o][r-(1ll<<o)+1]);
}
int check(db mid){
	s[0]=0;for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]*b[i];
	v[0]=0;for(int i=1;i<=n;i++)v[i]=v[i-1]+1.0*a[i]*(b[i]-mid);
	t[0]=0;for(int i=1;i<=n;i++)t[i]=t[i-1]+a[i];
	for(int i=0;i<=n+1;i++)f[0][i]=v[i];
	for(int j=1;(1ll<<j)<=n+2;j++){
		for(int i=0;i+(1ll<<j)-1<=n+1;i++){
			f[j][i]=min(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
	for(int i=1;i<=n;i++){
		if(s[i]<L)continue;
		if(v[i]-ask(lp[i],rp[i])>=eps)return 1;
	}
	return 0;
}
signed main(){
	n=read(),L=read(),R=read();
	Log[1]=0;for(int i=2;i<=n+2;i++)Log[i]=Log[i>>1]+1;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i]=read();
	s[0]=0;for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]*b[i];
	t[0]=0;for(int i=1;i<=n;i++)t[i]=t[i-1]+a[i];
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<=i&&s[i]-s[pos]>L)pos++;
		if(pos==0||pos>i||s[i]<L)continue;
		db len=1.0*(L-(s[i]-s[pos]))/b[pos];
		ans=max(ans,L*1.0/(t[i]-t[pos]+len));
	}
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<=i&&s[i]-s[pos]>R)pos++;
		if(pos==0||pos>i||s[i]<R)continue;
		db len=1.0*(R-(s[i]-s[pos]))/b[pos];
		ans=max(ans,R*1.0/(t[i]-t[pos]+len));
	}
	reverse(a+1,a+n+1);reverse(b+1,b+n+1);
	s[0]=0;for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]*b[i];
	t[0]=0;for(int i=1;i<=n;i++)t[i]=t[i-1]+a[i];
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<=i&&s[i]-s[pos]>L)pos++;
		if(pos==0||pos>i||s[i]<L)continue;
		db len=1.0*(L-(s[i]-s[pos]))/b[pos];
		ans=max(ans,L*1.0/(t[i]-t[pos]+len));
	}
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<=i&&s[i]-s[pos]>R)pos++;
		if(pos==0||pos>i||s[i]<R)continue;
		db len=1.0*(R-(s[i]-s[pos]))/b[pos];
		ans=max(ans,R*1.0/(t[i]-t[pos]+len));
	}
	reverse(a+1,a+n+1);reverse(b+1,b+n+1);
	s[0]=0;for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]*b[i];
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<i&&s[i]-s[pos]>R)pos++;
		lp[i]=pos;
	}
	s[0]=0;for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i]*b[i];
	pos=0;
	for(int i=1;i<=n;i++){
		while(pos<i&&s[i]-s[pos]>=L)pos++;
		rp[i]=pos-1;
	}
	db lv=0,rv=inf,res=0;
	for(int i=1;i<=100;i++){
		db mid=(lv+rv)/2.0;
		if(check(mid))res=mid,lv=mid;
		else rv=mid;
	}
	printf("%.8lf\n",max(res,ans));
	return 0;
}

C 【1005 B组】习惯孤独

CF1799H

后文中提到的在 \(u\) 节点“保留子树”是指在切了 \((u,fa_u)\) 这条边后选择递归到 \(u\) 的子树,反之则为“删除子树”。

考虑这样一个 dp:\(f_{u,S,x}\) 表示在以 \(u\) 为根的子树内,\(u\) 子树内所有点对应的操作集合是 \(S\),且最早一次保留子树是第 \(x\) 次操作(没有则为 \(k+1\))。

考虑怎么合并 \(f_{u,S,x}\)\(f_{v,T,y}\)。这两个状态能合并当且仅当满足以下条件:

  • \(S\cap T=\varnothing\)

  • \([x=k+1]+[y=k+1]\ge 1\)。这是因为如果 \(x\not=k+1\)\(y\not=k+1\),假设 \(x<y\),则第 \(x\) 次操作后只存在 \(x\) 子树内的点,不可能会有 \(y\) 操作了,反之亦然;

  • 如果 \(x\not=k+1\),则 \(\max\limits_{i\in T}\{i\}<x\)\(y\not=k+1\) 时则 \(\max\limits_{i\in S}\{i\}<y\)。因为如果执行过保留子树的操作后另一颗子树就不存在了。

此时 \(f_{u,S,x}\cdot f_{v,T,y}\) 可以转移到 \(f_{u,S+T,\min(x,y)}\)

合并完子树后考虑加入 \((u,fa_u)\) 这条边的影响。如果这条边上没有操作则答案不变,否则我们枚举是什么操作、是哪次操作。假设是第 \(i\) 次操作,\(f_{u,S,x}\) 有影响当且仅当:

  • 假如是保留子树,在 \(x>i\) 的时候可以转移到 \(f_{u,S+2^i,i}\)。因为如果先保留 \(u\) 子树内的子树后你就没办法保留 \(u\) 了;

  • 假如是删除子树,在 \(x=k+1\)\(\max\limits_{j\in S}\{j\}<i\) 时可以转移到 \(f_{u,S+2^i,x}\)。原因跟上面差不多。

注意这里还需要满足当前子树大小是否相同。注意到无论是保留还是删除,在第 \(i\) 次操作都没有保留子树操作。于是在原来的 \(siz_u\) 的基础上减去 \(i\) 之前删除子树的影响即可。复杂度 \(\mathcal{O}(n3^k+nk^22^k)\)

点击查看代码
#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;
}
struct edge{
	int v,nxt;
}e[10005];
int tot,head[5005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,ans,a[10],siz[5005],f[5005][70][10],g[70][10],p[70];
void dfs(int u,int fa){
	siz[u]=1;f[u][0][m+1]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u);siz[u]+=siz[v];
		for(int s=0;s<(1ll<<m);s++)for(int x=1;x<=m+1;x++)g[s][x]=0;
		for(int s=0;s<(1ll<<m);s++){
			int lft=(1ll<<m)-1-s;
			for(int x=1;x<=m;x++){
				if(!((s>>(x-1))&1ll))continue;
				for(int t=lft;;t=(t-1)&lft){
					if(p[t]<=x)g[s|t][x]=(g[s|t][x]+f[u][s][x]*f[v][t][m+1]%mod)%mod;
					if(!t)break;
				}
			}
			for(int t=lft;;t=(t-1)&lft){
				for(int y=1;y<=m;y++){
					if(!((t>>(y-1))&1ll))continue;
					if(p[s]<=y)g[s|t][y]=(g[s|t][y]+f[u][s][m+1]*f[v][t][y]%mod)%mod;
				}
				if(!t)break;
			}
			for(int t=lft;;t=(t-1)&lft){
				g[s|t][m+1]=(g[s|t][m+1]+f[u][s][m+1]*f[v][t][m+1]%mod)%mod;
				if(!t)break;
			}
		}
		for(int s=0;s<(1ll<<m);s++)for(int x=1;x<=m+1;x++)f[u][s][x]=g[s][x];
	}
	if(u!=1){
		for(int s=0;s<(1ll<<m);s++)for(int x=1;x<=m+1;x++)g[s][x]=f[u][s][x];
		for(int i=1;i<=m;i++){
			for(int s=0;s<(1ll<<m);s++){
				if((s>>(i-1))&1ll)continue;
				int now=siz[u];
				for(int j=1;j<i;j++)if((s>>(j-1))&1ll)now-=a[j-1]-a[j];
				for(int x=1;x<=m+1;x++){
					if(x!=m+1&&((s>>(x-1))&1ll)==0)continue;
					if(x>i&&now==a[i])g[s|(1ll<<(i-1))][i]=(g[s|(1ll<<(i-1))][i]+f[u][s][x])%mod;
					if(x==m+1&&p[s]<i&&now==a[i-1]-a[i])g[s|(1ll<<(i-1))][x]=(g[s|(1ll<<(i-1))][x]+f[u][s][x])%mod;				
				}
			}
		}
		for(int s=0;s<(1ll<<m);s++)for(int x=1;x<=m+1;x++)f[u][s][x]=g[s][x];		
	}
}
signed main(){
	n=read();for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	m=read();a[0]=n;for(int i=1;i<=m;i++)a[i]=read();
	for(int s=0;s<(1ll<<m);s++)p[s]=0;
	for(int s=0;s<(1ll<<m);s++)for(int i=1;i<=m;i++)if((s>>(i-1))&1ll)p[s]=i;
	dfs(1,0);for(int i=1;i<=m+1;i++)ans=(ans+f[1][(1ll<<m)-1][i])%mod;
	printf("%lld\n",ans);
	return 0;
}

D 【1005 B组】车站

不会最小树形图和可并堆。

C0331 【1007 B组】模拟测试

A 【1007 B组】特种训练

CF733E

发现这个路径就是一个来回折返的样子,推下式子再前缀和一下就做完了。\(\mathcal{O}(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;
}
int cnt[2][500005],sum[2][500005],id[2][500005];
int askc(int o,int l,int r){
	if(l>r)return 0;
	return cnt[o][r]-cnt[o][l-1];
}
int asks(int o,int l,int r){
	if(l>r)return 0;
	return sum[o][r]-sum[o][l-1];
}
char s[500005];
signed main(){
	int n=read();scanf("%s",s+1);
	for(int i=1;i<=n;i++){
		cnt[0][i]=cnt[0][i-1]+(s[i]=='L');
		cnt[1][i]=cnt[1][i-1]+(s[i]=='R');
		sum[0][i]=sum[0][i-1]+(s[i]=='L')*i;
		sum[1][i]=sum[1][i-1]+(s[i]=='R')*i;
		if(s[i]=='L')id[0][cnt[0][i]]=i;
		else id[1][cnt[1][i]]=i;
	}
	for(int i=1;i<=n;i++){
		if(s[i]=='R'){
			if(askc(1,1,i)<=askc(0,i+1,n)){
				printf("%lld ",2*(asks(0,id[0][askc(0,1,i)+1],id[0][askc(0,1,i)+askc(1,1,i)])-asks(1,1,i))+i);
			}
			else{
				printf("%lld ",2*(asks(0,i+1,n)-asks(1,id[1][askc(1,1,i)-askc(0,i+1,n)],i))+n+1+i);
			}
		}
		else{
			if(askc(0,i,n)<=askc(1,1,i-1)){
				printf("%lld ",2*(asks(0,i,n)-asks(1,id[1][askc(1,1,i-1)-askc(0,i,n)+1],i-1))+n+1-i);
			}
			else{
				printf("%lld ",2*(asks(0,i,id[0][askc(0,1,i)+askc(1,1,i-1)])-asks(1,1,i-1))-i);
			}
		}
	}
	return 0;
}

B 【1007 B组】红色改造

qoj5013

首先刚好分 \(k\) 段等价于至多分 \(k\) 段。然后有一个性质:如果把原序列 \(a\) 相邻相同颜色的格子合并成一段,最优方案是不会把完整的一段分割开的。不妨设这样合并后新序列 \(b\)\(m\) 个元素。

\(k=1\) 可以直接做。当 \(k>1\) 时最终的子序列中间一定会出现一段 \(0000\ldots1111\),此时它们可以是分出来的同一段,于是问题转化为在 \(b\) 中选一些元素,代价是长度总和,使它们构成的子序列在相邻相同元素合并后段数不超过 \(k+1\)。进一步的,这一步可以看成删除一些元素,要求长度总和尽量小。发现我们是不会删除两个相邻的元素的,因为 01 间隔,我们可以只删一个,让另一个去做贡献,这样一定不劣。于是问题得到进一步转化。

如果删了一个中间的元素,它会减少两个段,删边界的只会减少一个段。于是现在问题变成有 \(m\) 个元素,左右两边的代价是 1,中间的代价是 2,对于每个 \(x\) 需要选若干个价值和为 \(x\) 且互不相邻的元素,使得长度和最小。可以枚举左右两边删不删,剩下的就是反悔贪心板子了,可以 \(\mathcal{O}(n\log n)\)

点击查看代码
#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 s[2][300005],a[300005],b[300005],c[300005],L[300005],R[300005],ans[300005],vis[300005];
void solve(int l,int r,int cnt,int val){
	priority_queue<pii,vector<pii>,greater<pii> >q;
	for(int i=l;i<=r;i++)L[i]=i-1,R[i]=i+1,c[i]=b[i],q.push(mk(c[i],i)),vis[i]=0;
	c[0]=inf,L[l]=R[r]=0;
	while(!q.empty()){
		if(cnt<=0)break;
		pii u=q.top();q.pop();if(vis[u.se])continue;
		ans[cnt-1]=max(ans[cnt-1],val),cnt-=2,val-=u.fi;
		if(L[u.se])vis[L[u.se]]=1;
		if(R[u.se])vis[R[u.se]]=1;
		c[u.se]=c[L[u.se]]+c[R[u.se]]-c[u.se];
		if(L[L[u.se]])R[L[L[u.se]]]=u.se;
		if(R[R[u.se]])L[R[R[u.se]]]=u.se;
		L[u.se]=L[L[u.se]],R[u.se]=R[R[u.se]];
		q.push(mk(c[u.se],u.se));
	}
}
signed main(){
	int n=read(),m=0,cnt=1,res=-inf;
	for(int i=1;i<=n;i++)scanf("%1d",&a[i]);
	for(int i=2;i<=n;i++){
		if(a[i]==a[i-1])cnt++;
		else b[++m]=cnt,cnt=1;
	}
	b[++m]=cnt;
	for(int o=0;o<2;o++)for(int i=1;i<=n;i++)s[o][i]=s[o][i-1]+(a[i]==o);
	for(int i=0;i<=n;i++)res=max(res,s[0][i]+s[1][n]-s[1][i]);
	printf("%d ",res);for(int i=0;i<=m;i++)ans[i]=-inf;
	solve(3,m-2,m-2,n-b[1]-b[m]);solve(2,m-1,m,n);
	solve(3,m-1,m-1,n-b[1]);solve(2,m-2,m-1,n-b[m]);
	for(int i=2;i<=m-1;i++)ans[i]=max(ans[i],ans[i-1]),printf("%d ",ans[i]);
	for(int i=m;i<=n;i++)printf("%d ",n);
	return 0;
}

C 【1007 B组】势均力敌

agc028e

D 【1007 B组】车站爆破

⼀本通高手训练 栈的维护 或者 cqbz46698

考虑开一颗线段树,每个节点维护只处理当前区间之后的答案 \(s\),剩下多少辆车 \(c\),以及还需要删除多多少辆车 \(f\)。假设当前在节点 \(p\),那么 \(s(p)=g(ls(p),f(rs(p)))+s(rs(p))\),其中 \(g(u,k)\) 表示节点 \(u\) 删除末 \(k\) 辆车后的答案。考虑递归下去求 \(g(u,k)\)

  • \(c(rs(u))>k\),则左半边完整,\(g(u,k)=s(u)-s(rs(u))+g(rs(u),k)\)

  • 反之,右半边全部清完,\(g(u,k)=g(ls(u),k-c(rs(u))+f(rs(u)))\)

复杂度 \(\mathcal{O}(n\log^2n)\)

点击查看代码
#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,m,a[200005],b[200005];
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,c,f;
	}c[800005];
	int ask(int l,int r,int p,int k){
		if(l==r)return ((k==0)?c[p].s:0ll);
		int mid=(l+r)>>1;
		if(c[rs].c>k)return c[p].s-c[rs].s+ask(rson,k);
		else return ask(lson,k-c[rs].c+c[rs].f);
	} 
	void pushup(int l,int r,int p){
		int mid=(l+r)>>1;
		c[p].s=ask(lson,c[rs].f)+c[rs].s;
		c[p].c=max(0ll,c[ls].c-c[rs].f)+c[rs].c;
		c[p].f=c[ls].f+max(0ll,c[rs].f-c[ls].c);
	}
	void build(int l,int r,int p){
		if(l==r){
			if(a[l]==0)c[p]=(Node){b[l],1,0};
			else c[p]=(Node){0,0,b[l]};
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(l,r,p);
	}
	void update(int l,int r,int p,int x,int k,int v){
		if(l==r){
			if(k==0)c[p]=(Node){v,1,0};
			else c[p]=(Node){0,0,v};
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k,v);
		else update(rson,x,k,v);
		pushup(l,r,p);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read(),b[i]=read();
	Tr.build(1,n,1);
	for(int i=1;i<=m;i++){
		int qx=read(),qk=read(),qv=read();
		Tr.update(1,n,1,qx,qk,qv);
		printf("%lld\n",Tr.c[1].s);
	} 
	return 0;
}

C0335 【1010 B组】模拟测试

🤣👉️🤡

A 【1006 B组】道路

P4700 [CEOI2011] Traffic

缩点,然后现在变成求所有西侧点能到的点的点权和。这个问题似乎没有低于 \(\mathcal{O}(n^2)\) 的做法。

注意到题目中有一个不交的的限制。然后就有一个结论:在把那些所有西侧点都不能到达的东侧点删掉之后,假如一个点 \(i\) 能到达的东侧点中 \(y\) 最小是 \(L_i\),最大是 \(R_i\),则 \(y\)\([L_i,R_i]\) 间的所有东侧点都能被 \(i\) 到达。证明如下:

假如 \(i\) 能到达 \(L\)\(R\),但在这之间有一个点 \(x\) 不能被 \(i\) 到达。因为它没有被删去,所以存在一个点东侧点 \(j\) 能到达 \(x\)。然后考虑 \(i\to L\)\(i\to R\) 和东侧墙围成了一个多边形,此时不可能存在 \(j\) 能不穿过多边形而访问 \(x\)。就算 \(j\) 是穿过多边形上一个点 \(y\) 到达 \(x\) 的话,那么此时 \(i\) 也可以这么走到 \(x\)

点击查看代码
#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[1800005];
int tot,head[300005],deg[300005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
struct Node{
	int x,y,id;
}p[300005];
int cmp(Node x,Node y){
	if(x.x^y.x)return x.x<y.x;
	return x.y>y.y;
}
int sum[300005],L[300005],R[300005],eu[900005],ev[900005],ek[900005],vis[300005];
void dfs(int u){
	if(vis[u])return;
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;dfs(v);
	}
}
int dfn[300005],low[300005],in[300005],a[300005],val[300005],col[300005],rc,id;
stack<int>s;
void tarjan(int u){
	dfn[u]=low[u]=++id;s.push(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=s.top();s.pop();in[v]=0;col[v]=rc;val[rc]+=a[v];}while(u!=v);
	}
}
int rnk[300005];map<int,int>mp[300005];
signed main(){
	int n=read(),m=read(),a=read(),b=read();
	for(int i=1;i<=n;i++)p[i].x=read(),p[i].y=read(),p[i].id=i;
	sort(p+1,p+n+1,cmp);
	for(int i=1;i<=n;i++)rnk[p[i].id]=i;
	for(int i=1;i<=m;i++){
		eu[i]=rnk[read()],ev[i]=rnk[read()],ek[i]=read();
		if(ek[i]==1)add(eu[i],ev[i]);
		else add(eu[i],ev[i]),add(ev[i],eu[i]);
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=n;i++)if(p[i].x==0)dfs(i);
	tot=0;for(int i=1;i<=n;i++)head[i]=deg[i]=0;
	for(int i=1;i<=rc;i++){
		L[i]=inf,R[i]=-inf;
	}
	for(int i=1;i<=n;i++){
		if(p[i].x==a)L[col[i]]=min(L[col[i]],i),R[col[i]]=max(R[col[i]],i);
	}
	for(int i=1;i<=m;i++){
		if(col[eu[i]]!=col[ev[i]]&&!mp[col[ev[i]]].count(col[eu[i]]))add(col[ev[i]],col[eu[i]]),mp[col[ev[i]]][col[eu[i]]]=1;
	}
	queue<int>q;
	for(int i=1;i<=rc;i++)if(!deg[i])q.push(i);
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(p[i].x==a&&vis[i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			L[v]=min(L[v],L[u]),R[v]=max(R[v],R[u]);
			if((--deg[v])==0)q.push(v);
		}
	}
	for(int i=1;i<=n;i++){
		if(p[i].x==0){
			if(L[col[i]]==inf)puts("0");
			else printf("%lld\n",sum[R[col[i]]]-sum[L[col[i]]-1]);
		}
	}
	return 0;
}

B 【1006 B组】地震后的H市

P3343 [ZJOI2015] 地震后的幻想乡

好聪明的题。但是我真的好蠢。

考虑暴力:枚举所有边的大小情况,然后跑 kruskal,然后根据题目中的提示可以得到答案。这样是 \(\mathcal{O}(m!n)\) 的,可以过 50 分。

考虑优化:枚举全排列其实是完全不需要的,我们不需要知道所有边的大小关系,我们只关心在加入边权第几大的边时整张图恰好联通。令这最后一条边为第 \(c\) 大,这个概率记作 \(P(c=k)\),那么我们相当于求 \(\sum\limits_{k=0}^m k\cdot P(c=k)\)。套路的,这就等于 \(\sum\limits_{k=0}^mP(c>k)\),此时 \(P(c>k)\) 就等于加入前 \(k\) 大之后图依然不连通。

考虑状压 dp。定义 \(f_{mask,i}\) 表示在加入 \(i\) 条边后,这 \(i\) 条边都满足两个端点都在 \(mask\) 里,且 \(mask\) 点集不连通的方案数,\(g_{mask,i}\) 表示连通的方案数,\(siz(mask)\) 表示两端点都在 \(mask\) 这个点集里的边数。有转移式

\[f_{mask,i}=\sum\limits_{S\subsetneq mask\land p\in S}\sum\limits_{j=0}^{i}g_{S,j}\dbinom{siz(mask-S)}{i-j} \]

其中 \(p\) 表示 \(mask\) 中随便一个点。

为什么是这样的捏?首先我们要保证 \(mask\) 不连通,所以我们把 \(mask\) 分成 \(S\)\(mask-S\) 两个集合,\(mask-S\) 只能在内部随便连边,于是就保证了不连通。那为什么要求 \(S\) 连通和 \(p\in S\)?考虑不连通的 \(mask\) 点集,它可以被划分成若干个联通块。如果不要求 \(S\) 连通,则随便挑几个连通块组成 \(S\) 都是合法的,这样会算重;如果不加上 \(p\in S\) 的限制则每个连通块都会把当前局面算一遍,而强制钦定 \(p\in S\) 后只有 \(p\) 所在的连通块会计算一遍答案,于是就不重不漏了。(这步好妙啊,想了好久才想明白)

怎么求 \(g\)?显然 \(f_{mask,i}+g_{mask,i}=\dbinom{siz(mask)}{i}\)。于是做完了,复杂度 \(\mathcal{O}(3^nm^2)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
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,m,eu[55],ev[55],siz[1030],f[1030][55],g[1030][55],C[55][55],G[15][15];db ans; 
signed main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++)eu[i]=read()-1,ev[i]=read()-1,G[eu[i]][ev[i]]=1;
	for(int msk=0;msk<(1ll<<n);msk++){
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				if(((msk>>i)&1ll)&&((msk>>j)&1ll)&&G[i][j])siz[msk]++;
			}
		}
	}
	for(int i=0;i<=m;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++)C[i][j]=C[i-1][j-1]+C[i-1][j];
	}
	for(int i=0;i<(1ll<<n);i++)f[i][0]=1;
	for(int i=0;i<n;i++)f[1ll<<i][0]=0,g[1ll<<i][0]=1;
	for(int i=1;i<(1ll<<n);i++){
		for(int j=1;j<=m;j++){
			int p=i&-i;
			for(int k=(i-1)&i;k;k=(k-1)&i){
				if(!(k&p))continue;
				for(int l=0;l<=j;l++){
					f[i][j]+=g[k][l]*C[siz[i-k]][j-l];
				}
			}
			g[i][j]=C[siz[i]][j]-f[i][j];
		}
	}
	for(int i=0;i<=m;i++)ans+=1.0*f[(1ll<<n)-1][i]/C[m][i];
	printf("%.6lf\n",ans/(m+1));
	return 0;
}

C 【1006 B组】求和

BZOJ4262. Sum

有一个 \(\mathcal{O}(T\log n)/\mathcal{O}(T\log^2n)\) 的做法。以最大值为例,考虑单调栈求一个点左右最近的两个大于 \(a_i\) 的点 \(L_i,R_i\),那么左端点在 \((L_i,i]\) 间,右端点在 \([i,R_i)\) 间的所有区间的最大值都是 \(a_i\)。于是就变成矩阵加,矩阵求和的问题。可以离线扫描线,也可以直接树套树。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int n=1e5,inf=1e18,mod=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 T,lc[20000005],rc[20000005];
struct ST{
	int v[20000005],t[20000005];
	void pushup(int p){
		v[p]=v[lc[p]]+v[rc[p]];
	}
	void pushdown(int l,int r,int p){
		if(!t[p])return;
		int mid=(l+r)>>1;
		if(!lc[p])lc[p]=++T;
		if(!rc[p])rc[p]=++T;
		v[lc[p]]+=(mid-l+1)*t[p],v[rc[p]]+=(r-mid)*t[p];
		t[lc[p]]+=t[p],t[rc[p]]+=t[p];
		t[p]=0;
	}
	void add(int l,int r,int &p,int L,int R,int k){
		if(L>R)return;
		if(!p)p=++T;
		if(L<=l&&r<=R){
			v[p]+=(r-l+1)*k,t[p]+=k;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(l,mid,lc[p],L,R,k);
		if(R>mid)add(mid+1,r,rc[p],L,R,k);
		pushup(p);
	}
	int ask(int l,int r,int p,int L,int R){
		if(L>R)return 0;
		if(!p)return 0;
		if(L<=l&&r<=R){
			return v[p];
		}
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res+=ask(l,mid,lc[p],L,R);
		if(R>mid)res+=ask(mid+1,r,rc[p],L,R);
		return res;
	}
}A,B;
int root[800005];
void add(int l,int r,int p,int l1,int r1,int l2,int r2,int k){
	if(l1>r1||l2>r2)return;
	if(l1<=l&&r<=r1){
		A.add(1,n,root[p],l2,r2,k);
		return;
	}
	int mid=(l+r)>>1;B.add(1,n,root[p],l2,r2,k*(min(r,r1)-max(l,l1)+1));
	if(l1<=mid)add(l,mid,p<<1,l1,r1,l2,r2,k);
	if(r1>mid)add(mid+1,r,p<<1|1,l1,r1,l2,r2,k);
}
int ask(int l,int r,int p,int l1,int r1,int l2,int r2){
	if(l1>r1||l2>r2)return 0;
	if(l1<=l&&r<=r1){
		return A.ask(1,n,root[p],l2,r2)*(r-l+1)+B.ask(1,n,root[p],l2,r2);
	}
	int mid=(l+r)>>1,res=A.ask(1,n,root[p],l2,r2)*(min(r,r1)-max(l,l1)+1);
	if(l1<=mid)res+=ask(l,mid,p<<1,l1,r1,l2,r2);
	if(r1>mid)res+=ask(mid+1,r,p<<1|1,l1,r1,l2,r2);
	return res;
}	
int m,top,a[100005],q[100005],l1[40005],r1[40005],l2[40005],r2[40005],L[100005],R[100005];
signed main(){
	for(int i=1,a1=1,a2=1;i<=n;i++)a1=a1*1023%mod,a2=a2*1025%mod,a[i]=a1^a2;
	m=read();
	for(int i=1;i<=m;i++)l1[i]=read(),r1[i]=read(),l2[i]=read(),r2[i]=read();
	a[0]=inf,top=0,q[++top]=0;
	for(int i=1;i<=n;i++){
		while(top&&a[q[top]]<=a[i])top--;
		L[i]=q[top]+1,q[++top]=i;
	}
	a[n+1]=inf,top=0,q[++top]=n+1;
	for(int i=n;i>=1;i--){
		while(top&&a[q[top]]<a[i])top--;
		R[i]=q[top]-1,q[++top]=i;
	}
	for(int i=1;i<=n;i++){
		L[i]=max(L[i],1ll),R[i]=min(R[i],n);
		if(L[i]<=R[i])add(1,n,1,L[i],i,i,R[i],a[i]);
	}
	a[0]=-inf,top=0,q[++top]=0;
	for(int i=1;i<=n;i++){
		while(top&&a[q[top]]>=a[i])top--;
		L[i]=q[top]+1,q[++top]=i;
	}
	a[n+1]=-inf,top=0,q[++top]=n+1;
	for(int i=n;i>=1;i--){
		while(top&&a[q[top]]>a[i])top--;
		R[i]=q[top]-1,q[++top]=i;
	}
	for(int i=1;i<=n;i++){
		L[i]=max(L[i],1ll),R[i]=min(R[i],n);
		if(L[i]<=R[i])add(1,n,1,L[i],i,i,R[i],-a[i]);
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ask(1,n,1,l1[i],r1[i],l2[i],r2[i]));
	return 0;
}

还有一个别的做法。考虑先把询问 \([l_1,r_1][l_2,r_2]\) 拆成 \([l_1,r_1][1,r_2]-[l_1,r_1][1,l_2)\),然后把所有询问按右端点区间的右端点从小到大排序,从小到大枚举右端点处理。以最大值为例,假设当前枚举到 \(r\),现在每个位置 \(i\) 维护 \(\sum\limits_{j=i}^r\max\limits_{k=i}^j\{a_k\}\),考虑怎么维护新加入的贡献。

开一个单调栈,那么每个数会成为一段时间内的一段区间的后缀最值。假如在加入 \(a_j\) 时这个数 \(a_i\) 被弹出,那么当时间 \(r\)\(i\) 之前时没有影响,在 \(j\) 之后影响为 \((j-i)a_i\),在 \([i,j)\) 内时影响是 \((r-i+1)a_i\)。于是可以拆成 \(ra_i+(-i+1)a_i\),然后分别维护,在入栈和出栈的时候修改一下就行了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int n=1e5,inf=1e18,mod=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 BIT{
	int c1[2][100005],c2[2][100005];
	void add(int o,int x,int v){
		int y=x;
		for(;x<=n;x+=x&-x)c1[o][x]+=v,c2[o][x]-=y*v;
	}
	void add(int o,int l,int r,int v){
		add(o,l,v),add(o,r+1,-v);
	}
	int ask(int o,int x){
		int res=0,y=x;
		for(;x;x-=x&-x)res+=(y+1)*c1[o][x]+c2[o][x];
		return res;
	}
	int ask(int o,int l,int r){
		return ask(o,r)-ask(o,l-1);
	}	
}Tmx,Tmn;
struct Que{
	int o,ql,qr,r,id;
}qu[80005];
int m,tot,topmx,topmn,a[100005],ans[40005],qmx[100005],qmn[100005],l1[40005],r1[40005],l2[40005],r2[40005];
vector<int>v[100005];
signed main(){
	for(int i=1,a1=1,a2=1;i<=n;i++){
		a1=a1*1023%mod,a2=a2*1025%mod,a[i]=a1^a2;
	}
	m=read();
	for(int i=1;i<=m;i++){
		l1[i]=read(),r1[i]=read(),l2[i]=read(),r2[i]=read();
	}
	for(int i=1;i<=m;i++){
		qu[++tot]=(Que){-1,l1[i],r1[i],l2[i]-1,i};
		qu[++tot]=(Que){1,l1[i],r1[i],r2[i],i};
	}
	for(int i=1;i<=tot;i++){
		v[qu[i].r].push_back(i);
	}
	for(int r=0;r<=n;r++){
		while(topmx&&a[qmx[topmx]]<=a[r]){
			int R=qmx[topmx],L=qmx[topmx-1]+1;topmx--;
			Tmx.add(0,L,R,-a[R]);Tmx.add(1,L,R,(r-1)*a[R]);
		}
		{
			int L=qmx[topmx]+1,R=r;
			Tmx.add(0,L,R,a[R]);Tmx.add(1,L,R,(-r+1)*a[R]);
			qmx[++topmx]=r;
		}
		while(topmn&&a[qmn[topmn]]>=a[r]){
			int R=qmn[topmn],L=qmn[topmn-1]+1;topmn--;
			Tmn.add(0,L,R,-a[R]);Tmn.add(1,L,R,(r-1)*a[R]);
		}
		{
			int L=qmn[topmn]+1,R=r;
			Tmn.add(0,L,R,a[R]);Tmn.add(1,L,R,(-r+1)*a[R]);
			qmn[++topmn]=r;
		}
		for(auto x:v[r]){
			int ql=qu[x].ql,qr=qu[x].qr;
			ans[qu[x].id]+=qu[x].o*(Tmx.ask(0,ql,qr)*r+Tmx.ask(1,ql,qr));
			ans[qu[x].id]-=qu[x].o*(Tmn.ask(0,ql,qr)*r+Tmn.ask(1,ql,qr));
		}
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

D 【1006 B组】最短路

P6890 [CEOI2006] Link

这是一个内向基环树森林,考虑对一棵树怎么做。首先加边肯定是连 1 和一个点最优。我们把环找出来,然后对环外的点,我们连叶子和 1(如果这个点就是 1 当然就不连了),然后删掉它往上 \(k-1\) 个点。然后考虑环上的问题:一个环上有若干个合法/不合法的点,你每次操作可以选连续的 \(k\) 个点,把它们全部变合法,求最小操作次数让所有的都合法。

套路的,先断环成链。然后最优的方案一定是每次从一个不合法的点开始覆盖。于是我们枚举第一次选的点,然后贪心往后跳。复杂度 \(\mathcal{O}(\dfrac{n^2}{k})\),过不了。但是你发现很多枚举是不必要的,我们只用枚举到前 \(k\) 个不合法的点,后面的跟前面的方案是等价的。于是复杂度优化到 \(\mathcal{O}(k\times \dfrac{n}{k})=\mathcal{O}(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[500005];
int tot,head[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,k,ans,vis[500005],d[500005],cyc[500005],c[500005],go[500005],cov[2000005],nxt[2000005];
void walk(int u){
	vis[u]=1,d[u]=inf;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(cyc[v])continue;
		walk(v);d[u]=min(d[u],d[v]+1);
	}
	if(u==1)d[u]=0;
	if(!cyc[u]&&d[u]>k)d[u]=1,ans++;
}
signed main(){
	n=read(),k=read();
	for(int i=1,x,y;i<=n;i++)x=read(),y=read(),go[x]=y,add(y,x);
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		int tot=0,x=i;while(!vis[x])vis[x]=1,x=go[x];
		int y=x;while(go[x]!=y)c[++tot]=x,x=go[x];
		c[++tot]=x;
		for(int j=1;j<=tot;j++)cyc[c[j]]=1;
		for(int j=1;j<=tot;j++)walk(c[j]);
		for(int j=1;j<=tot*2;j++)cov[j]=0;
		for(int j=1;j<=tot;j++){
			if(d[c[j]]>k)continue;
			cov[j]++,cov[j+k-d[c[j]]+1]--;
		}
		for(int j=1;j<=tot*2;j++)cov[j]+=cov[j-1];
		for(int j=1;j<=tot;j++)cov[j]=cov[j+tot]=(cov[j]|cov[j+tot]);
		nxt[tot*2+1]=tot*2+1; 
		for(int j=tot*2;j>=1;j--){
			if(cov[j])nxt[j]=nxt[j+1];
			else nxt[j]=j;
		}
		int res=inf;
		for(int j=1,cnt=0;j<=tot&&cnt<=k;j++){
			if(cov[j])continue;
			int u=j,tmp=0;
			while(u<j+tot){
				tmp++,u+=k;
				if(u>=j+tot)break;
				u=nxt[u];
			}
			res=min(res,tmp),cnt++;
		}
		if(res!=inf)ans+=res;
	}
	printf("%lld\n",ans);
	return 0;
}

C0342 【1014 B组】模拟测试

🤣👉️🤡

A 【1014 B组】征服

ABC251F。没删调试怒挂 100。

就是 dfs 树和 bfs 树。

点击查看代码
#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 n,m,cnt,vis[200005],eu[200005],ev[200005];
void dfs(int u){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(vis[v])continue;
		eu[++cnt]=u,ev[cnt]=v;dfs(v);
	}
}
void bfs(int s){
	cnt=0;for(int i=1;i<=n;i++)vis[i]=0;
	queue<int>q;q.push(s);vis[s]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;if(vis[v])continue;
			eu[++cnt]=u,ev[cnt]=v,vis[v]=1;q.push(v);
		}
	}
}
signed main(){
	n=read(),m=read();
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(),add(u,v),add(v,u);
	}
	dfs(1);for(int i=1;i<n;i++)printf("%lld %lld\n",eu[i],ev[i]);
	bfs(1);for(int i=1;i<n;i++)printf("%lld %lld\n",eu[i],ev[i]);
	return 0;
}

B 【1014 B组】Source

agc054c。牛牛题,但感觉考场上没找到规律有点蠢了。

cmd 的博客 吧,我也没太懂证明。

点击查看代码
#include<bits/stdc++.h>
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 p[5005];
signed main(){
	int n=read(),k=read(),ans=1;
	for(int i=1;i<=n;i++){
		int c=0;p[i]=read();
		for(int j=1;j<i;j++)c+=(p[j]>p[i]);
		if(c==k)ans=1ll*ans*(n-i+1)%mod;
	}
	printf("%d\n",ans);
	return 0;
}

C 【1014 B组】群星

P9340 [JOISC 2023 Day3] Tourism。挺傻逼的题,不知道自己为什么脑抽了。

考虑一个经典结论:树上包含 \(k\) 个点 \(p_i\) 的最小联通块边数就是按 dfs 序排序后相邻(首尾也算相邻)两点的距离和的一半。想到这个就很傻逼了,考虑莫队。可以直接压位 trie 小常数 \(\mathcal{O}(n\sqrt{n}\log_wn)\),也可以像秃子酋长一样搞一个只删的回滚莫队,链表维护,大常数 \(\mathcal{O}(n\sqrt{n})\)

压位 trie:

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned 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;
}
#define clz(x) (__builtin_clzll(x))
#define ctz(x) (__builtin_ctzll(x))
ull BUFF[1<<25],*BT=BUFF+sizeof(BUFF)/sizeof(ull);
ull *alloc(int siz){return BT-=siz;}
const int g=6,mod=(1<<g)-1;
int dep;ull *c[5];
void init(int siz){
	for(dep=1;;dep++){
		int cnt=(siz+(1ull<<g*dep)-1)>>g*dep;
		c[dep-1]=alloc(cnt);
		if(cnt==1)return;
	}
}	
void insert(int x){
	for(int i=0;i<dep;i++){
		ull p=1ull<<(x>>i*g&mod);
		if(c[i][x>>(i+1)*g]&p)return;
		c[i][x>>(i+1)*g]|=p;
	}
}
void erase(int x){
	for(int i=0;i<dep;i++)
		if(c[i][x>>(i+1)*g]&=~(1ull<<(x>>i*g&mod)))return;
}
int getpre(int x){
	for(int i=0;i<dep;i++){
		int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
		if(v&((1ull<<cur)-1)){
			int res=x>>(i+1)*g<<(i+1)*g;res+=(mod-clz(v&((1ull<<cur)-1)))<<i*g;
			for(int j=i-1;j>=0;j--)res+=(mod-clz(c[j][res>>(j+1)*g]))<<j*g;
			return res;
		}
	}
	return -1;
}	
int getsuf(int x){
	for(int i=0;i<dep;i++){
		int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
		if(v>>cur>1){
			int res=x>>(i+1)*g<<(i+1)*g;res+=(ctz(v>>(cur+1))+cur+1)<<i*g;
			for(int j=i-1;j>=0;j--)res+=ctz(c[j][res>>(j+1)*g])<<j*g;
			return res;
		}
	}
	return -1;
}
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,dfn[100005],rnk[100005],f[20][100005],d[100005];
int getmin(int x,int y){
	return (dfn[x]<dfn[y])?x:y;
}
void dfs(int u,int fa){
	dfn[u]=++cur,rnk[cur]=u,f[0][cur]=fa,d[u]=d[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 Log[100005];
int getlca(int u,int v){
	if(u==v)return v;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=Log[v-u++];
	return getmin(f[o][u],f[o][v-(1<<o)+1]);
}
int dist(int x,int y){
	return d[x]+d[y]-2*d[getlca(x,y)];
}
int a[100005],bel[100005],bl[505],br[505];
struct Que{
	int l,r,id;
}qu[100005];
int cmp(Que x,Que y){
	if(bel[x.l]^bel[y.l])return bel[x.l]<bel[y.l];
	return ((bel[x.l]&1)?(x.r<y.r):(x.r>y.r));
}
int mx=-inf,mn=inf,res,ans[100005],B[100005];
void add(int x){
	B[x]++;
	if(B[x]!=1)return;
	int pre=getpre(dfn[x]),suf=getsuf(dfn[x]);
	if(pre!=-1)pre=rnk[pre];
	else mn=x;
	if(suf!=-1)suf=rnk[suf];
	else mx=x;
	if(pre!=-1)res+=dist(x,pre);
	if(suf!=-1)res+=dist(x,suf);
	if(pre!=-1&&suf!=-1)res-=dist(pre,suf);
	insert(dfn[x]);
}
void del(int x){
	B[x]--;
	if(B[x]!=0)return;
	int pre=getpre(dfn[x]),suf=getsuf(dfn[x]);
	if(pre!=-1)pre=rnk[pre];
	if(suf!=-1)suf=rnk[suf];
	if(x==mn)mn=(suf!=-1)?suf:inf;
	if(x==mx)mx=(pre!=-1)?pre:-inf;
	if(pre!=-1)res-=dist(x,pre);
	if(suf!=-1)res-=dist(x,suf);
	if(pre!=-1&&suf!=-1)res+=dist(pre,suf);
	erase(dfn[x]);
}
signed main(){
	int n=read(),m=read(),q=read();init(n);
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs(1,0);
	for(int j=1;j<=Log[n];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(int i=1;i<=m;i++)a[i]=read();
	int siz=(int)sqrt(m),num=(m+siz-1)/siz;
	for(int i=1;i<=m;i++)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;i++)bl[i]=(i-1)*siz+1,br[i]=i*siz;
	br[num]=m;
	for(int i=1,l,r;i<=q;i++)l=read(),r=read(),qu[i]=(Que){l,r,i};
	sort(qu+1,qu+q+1,cmp);
	for(int i=1,L=1,R=0;i<=q;i++){
		int l=qu[i].l,r=qu[i].r,id=qu[i].id;
		while(L>l)add(a[--L]);
		while(R<r)add(a[++R]);
		while(L<l)del(a[L++]);
		while(R>r)del(a[R--]);
		ans[id]=(res+dist(mn,mx))/2+1;
	}	
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
	return 0;
}

回滚莫队:

点击查看代码
#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[200005];
int tot,head[100005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int cur,dfn[100005],rnk[100005],f[20][100005],d[100005];
int getmin(int x,int y){
	return (dfn[x]<dfn[y])?x:y;
}
void dfs(int u,int fa){
	dfn[u]=++cur,rnk[cur]=u,f[0][cur]=fa,d[u]=d[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 Log[100005];
int getlca(int u,int v){
	if(u==v)return v;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=Log[v-u++];
	return getmin(f[o][u],f[o][v-(1<<o)+1]);
}
int dist(int x,int y){
	return d[x]+d[y]-2*d[getlca(x,y)];
}
int n,m,q,a[100005],ans[100005];
struct Que{
	int l,r,id;
}qu[100005];
int siz,num,bel[100005],L[505],R[505];
int cmp(Que x,Que y){
	if(bel[x.l]!=bel[y.l])return bel[x.l]<bel[y.l];
	else return x.r>y.r;
}
int res,pre[100005],nxt[100005],B[100005];
void del(int x){
	B[x]--;
	if(!B[x]){
		res-=dist(pre[x],x)+dist(x,nxt[x]);
		res+=dist(pre[x],nxt[x]);
		pre[nxt[x]]=pre[x],nxt[pre[x]]=nxt[x]; 
	}
}
struct Node{
	int a,b,c;
};
signed main(){
	n=read(),m=read(),q=read();
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
	dfs(1,0);
	for(int j=1;j<=Log[n];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(int i=1;i<=m;i++)a[i]=read();
	siz=(int)sqrt(m),num=(m+siz-1)/siz;
	for(int i=1;i<=m;i++)bel[i]=(i-1)/siz+1;
	for(int i=1;i<=num;i++)L[i]=(i-1)*siz+1,R[i]=i*siz;
	R[num]=m;
	for(int i=1,l,r;i<=q;i++)l=read(),r=read(),qu[i]=(Que){l,r,i};
	sort(qu+1,qu+q+1,cmp);
	for(int j=1,i=1;j<=num;j++){
		int l=L[j],r=m,lst=0;res=0;
		for(int k=1;k<=n;k++)B[k]=pre[k]=nxt[k]=0;
		for(int k=L[j];k<=m;k++)B[a[k]]++;
		for(int k=n;k>=1;k--)if(B[rnk[k]]){lst=k;break;}
		for(int k=1;k<=n;k++){
			if(!B[rnk[k]])continue;
			pre[rnk[k]]=rnk[lst];
			nxt[rnk[lst]]=rnk[k];
			lst=k;
		}
		for(int k=1;k<=n;k++){
			if(B[k])res+=dist(k,nxt[k]);
		}
		while(i<=q){
			int ql=qu[i].l,qr=qu[i].r,id=qu[i].id;if(bel[ql]!=j)break;
			while(r>qr)del(a[r--]);
			int tmp=res;stack<Node>s;
			while(l<ql)s.push((Node){a[l],pre[a[l]],nxt[a[l]]}),del(a[l++]);
			ans[id]=res/2+1;res=tmp;l=L[j];
			while(!s.empty()){
				Node u=s.top();s.pop();B[u.a]++;
				if(B[u.a]==1)nxt[u.b]=u.a,pre[u.a]=u.b,pre[u.c]=u.a,nxt[u.a]=u.c;
			}
			i++;
		}
	}
	for(int i=1;i<=q;i++)printf("%d\n",ans[i]);	
	return 0;
}

还有 \(\mathcal{O}(n\log^2n)\) 的做法,我不会,看题解吧。

D 【1014 B组】随机区分

abc245ex

拿到没什么思路啊,\(\mathcal{O}(n^3)\) 的 dp 优化成矩乘没什么前途的样子,考虑一个部分分的写。

\(m\) 是质数怎么做?首先 \(n=0\) 就容斥一下变成 \(m^k-(m-1)^k\),剩下 \(n\neq 0\) 的时候你发现不管你前面怎么选,都可以用最后一个数变成 \(n\),而且这是一一对应的,所以方案数是 \((m-1)^{k-1}\)

\(m\)\(p^e\) 怎么做?先考虑 \(n=r\cdot p^t\neq 0\)。这个时候我们相当于把 \(t\)\(p\) 塞到 \(k\) 次操作里,这就是一个插板法。然后剩下的东西就互质了,就变成上面的情况了,答案就是 \(\dbinom{t+k-1}{k-1}(m-\dfrac{m}{p})^{k-1}\)。注意到前面的组合数中 \(k\) 可能很大,完全没法计算,但是 \(\dbinom{t+k-1}{k-1}=\dbinom{t+k-1}{t}\),于是可以直接暴力计算下降幂。\(n=0\) 怎么搞?我们可以容斥一下,减去不为 0 的方案。于是枚举 \(t\),此时 \(r\) 可以任取,但不能是 \(p\) 的倍数。

最后是 \(m=\prod p_i^{e_i}\)。考虑拆开 \(m\),然后沿用上面的做法。然后你会惊奇的发现,令 \(n\gets n\bmod p^{e_i},m\gets p_i^{e_i}\) 的答案为 \(f(p_i^{e_i})\),最后答案就是 \(\prod f(p_i^{e_i})\)。为啥?你对每个 \(m_i=p_i^{e_i}\) 都会求出一组解 \(\prod a_{i,j}\equiv n\bmod m_i\pmod {m_i}\),而他们全部乘起来是能得到一组方案的;同时,一个合法的方案也可以生成一组 \(a_{i,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;b%=mod;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
}
int iv[105];
int C(int n,int m){
	int res=1;
	for(int i=n;i>=n-m+1;i--)res=res*i%mod;
	for(int i=1;i<=m;i++)res=res*iv[i]%mod;
	return res;
} 
int f(int k,int n,int m,int p,int e){
	if(n==0){
		int res=qpow(m,k);
		for(int i=0;i<e;i++){
			res=(res-(qpow(p,e-i)-qpow(p,e-i-1)+mod)%mod*C(i+k-1,i)%mod*qpow(m-m/p,k-1)%mod+mod)%mod;
		}
		return res;
	}
	int r=n,t=0;while(r%p==0)r/=p,t++;
	return C(t+k-1,t)*qpow(m-m/p,k-1)%mod;
}
signed main(){
	int k=read(),n=read(),m=read(),ans=1;
	iv[1]=1;for(int i=2;i<=100;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	for(int i=2;i*i<=m;i++){
		if(m%i)continue;
		int cnt=0,pw=1;while(m%i==0)cnt++,m/=i,pw*=i;
		ans=ans*f(k,n%pw,pw,i,cnt)%mod;
	}
	if(m>1)ans=ans*f(k,n%m,m,m,1)%mod;
	printf("%lld\n",ans);
	return 0;
}

C0344 【1016 B组】模拟测试

考试的时候没看清 T2 数据范围,胡了个 \(\mathcal{O}(n)\) 假做法,寄了。

A 【1016 B组】路径

topsort。

点击查看代码
#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[1000005];
int tot,head[1000005],deg[1000005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot,deg[v]++;
}
int f[1000005],tag[1000005],c[1000005];
void solve(){
	int n=read(),m=read(),flag=0;
	for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(v,u);
	int k=read();
	for(int i=1;i<=k;i++)c[i]=read(),tag[c[i]]=f[c[i]]=1;
	queue<int>q;
	for(int i=1;i<=n;i++)if(!deg[i])q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;f[v]=max(f[v],f[u]+tag[v]);
			if((--deg[v])==0)q.push(v);
		}
	} 
	for(int i=1;i<=n;i++)if(f[i]==k)flag=1;
	puts(flag?"Yes":"No");
	tot=0;for(int i=1;i<=n;i++)head[i]=deg[i]=f[i]=tag[i]=0;
}
signed main(){
	int T=read();
	while(T--){
		solve();
	} 
	return 0;
}

B 【1016 B组】异或

首先区间异或可以看成在差分序列上的单点修改,然后现在变成给你一个序列,每次可以选两个数让它们异或上同一个数,求让整个序列变成 0 的最小次数。因为整个序列异或起来为 0,所以一定是有解的。注意到一个大小为 \(k\) 的异或和为 0 的子集的最少操作次数是 \(k-1\),于是状压 dp 做完了。复杂度 \(\mathcal{O}(3^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;
}
int a[25],c[25],f[270005],s[270005],t[270005];
signed main(){
	int n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	n++;for(int i=1;i<=n;i++)c[i]=a[i]^a[i-1];
	for(int i=0;i<(1ll<<n);i++){
		for(int j=1;j<=n;j++){
			if((i>>(j-1))&1ll)s[i]^=c[j],t[i]++;
		}
	}
	f[0]=0;
	for(int i=1;i<(1ll<<n);i++){
		f[i]=inf;
		for(int j=i;j;j=(j-1)&i){
			if(s[j]==0)f[i]=min(f[i],f[i-j]+t[j]-1); 
		} 
	}
	printf("%lld\n",f[(1ll<<n)-1]);
	return 0;
} 

C 【1016 B组】距离

不会点分树/kk。

看部分分 \(b=1\)。问题变成往点集插入一个点,或者询问距离某个点最近的点的距离。考虑点分树。在每个重心维护其点分树子树内具其最近的点的距离,查询时直接查就好了。因为保证边权为正,且求的是距离之和最小,所以直接做就是对的了,\(\mathcal{O}(n\log n)\)

现在二维的情况怎么做?先把询问离线,然后把询问和修改都挂到它们在点分树上的祖先处,然后在交界处更新答案。复杂度 \(\mathcal{O}(n\log^2 n)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll 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,w,nxt;
}e[400005];
int tot,head[200005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int cur,dfn[200005],f[20][200005];ll dis[200005];
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;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;if(v==fa)continue;
		dis[v]=dis[u]+w;dfs(v,u);
	}
}
int Log[200005];
int getlca(int u,int v){
	if(u==v)return v;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=Log[v-u++];
	return getmin(f[o][u],f[o][v-(1<<o)+1]);
}
ll dist(int u,int v){
	return dis[u]+dis[v]-2ll*dis[getlca(u,v)];
}
int root,siz[200005],vis[200005],Fa[200005],mx[200005];
void dfssiz(int u,int fa){
	siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa||vis[v])continue;
		dfssiz(v,u);siz[u]+=siz[v];
	}
}
void dfsroot(int u,int fa,int sum){
	mx[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa||vis[v])continue;
		dfsroot(v,u,sum);mx[u]=max(mx[u],siz[v]);
	}
	mx[u]=max(mx[u],sum-siz[u]);
	if(mx[root]>mx[u])root=u;
}
void dfsbuild(int u){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(vis[v])continue;
		dfssiz(v,u);
		root=0;dfsroot(v,u,siz[v]);
		Fa[root]=u,dfsbuild(root);
	}
}
struct Que{
	int op,x,y,id;
}q[200005];
vector<int>v[200005];
ll val[200005],ans[200005];
void solve(int p){
	for(auto x:v[p]){
		if(q[x].op==1){
			int u=q[x].y;
			while(u)val[u]=min(val[u],dist(q[x].x,p)+dist(q[x].y,u)),u=Fa[u];
		}
		else{
			int u=q[x].y;
			while(u)ans[q[x].id]=min(ans[q[x].id],val[u]+dist(q[x].x,p)+dist(q[x].y,u)),u=Fa[u];			
		}
	}
	for(auto x:v[p]){
		if(q[x].op==1){
			int u=q[x].y;
			while(u)val[u]=inf,u=Fa[u];
		}
	}
}
signed main(){
	int n=read(),m=read();
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	for(int i=1,u,v,w;i<n;i++)u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
	dfs(1,0);for(int j=1;j<=Log[n];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))]);
	mx[0]=n+5;dfssiz(1,0);root=0,dfsroot(1,0,n);dfsbuild(root);
	for(int i=1;i<=m;i++)q[i].op=read(),q[i].x=read(),q[i].y=read(),q[i].id=i,ans[i]=inf;
	for(int i=1;i<=m;i++){int u=q[i].x;while(u)v[u].push_back(i),u=Fa[u];}
	for(int i=1;i<=n;i++)val[i]=inf;
	for(int i=1;i<=n;i++)solve(i);
	for(int i=1;i<=m;i++)if(q[i].op==2)printf("%lld\n",((ans[i]>=inf)?-1:ans[i]));
	return 0;
}

D 【1016 B组】花之舞

很智慧的一道题。同类题:P9062

区间平面最近点对,很厉害啊,感觉人类智慧和分治都不太行的样子。先不考虑删点。依照最近点对一类问题的通用思路,我们找有效点对。考虑枚举一个 \(k\),把整个平面分成大小为 \(2^k\times 2^k\) 的小正方形,那么如果在这个正方形内存在超过 4 个点,那么一定有任意两点的距离小于 \(2^{k-1}\) 了对吧,所以我们对于每个点每个 \(k\) 只用往左往右找常数个点,把这些点对保留即可。那么现在问题变成了二维数点,就很简单了。

然后加上删点操作。假设区间最近点对是 \((x,y)\),我们删点一定只会删除 \(x\) 或者 \(y\) 对吧,所以你在原来的线段树上额外维护一下删了最近点对左端点/右端点后的最近点对就好了。

略微卡常?实现的时候可以微调常数获得更好的时间。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9,V=1e8;
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;
}
ll gethash(int x,int y){
	return 1ll*x*(V+5)+y;
}
struct Pair{
	int a,b,dis;
};
Pair getmin(Pair x,Pair y){
	return ((x.dis<=y.dis)?x:y);
}
struct Node{
	Pair p,lp,rp;
	Node operator +(const Node &b)const{
		Node c;c.p=getmin(p,b.p);
		if(c.p.a==p.a){
			if(c.p.a==b.p.a)c.lp=getmin(lp,b.lp);
			else if(c.p.a==b.p.b)c.lp=getmin(lp,b.rp);
			else c.lp=getmin(lp,b.p);
		}
		else if(c.p.a==p.b){
			if(c.p.a==b.p.a)c.lp=getmin(rp,b.lp);
			else if(c.p.a==b.p.b)c.lp=getmin(rp,b.rp);
			else c.lp=getmin(rp,b.p);
		}
		else{
			if(c.p.a==b.p.a)c.lp=getmin(p,b.lp);
			else if(c.p.a==b.p.b)c.lp=getmin(p,b.rp);
			else c.lp=getmin(p,b.p);
		}
		if(c.p.b==p.a){
			if(c.p.b==b.p.a)c.rp=getmin(lp,b.lp);
			else if(c.p.b==b.p.b)c.rp=getmin(lp,b.rp);
			else c.rp=getmin(lp,b.p);
		}
		else if(c.p.b==p.b){
			if(c.p.b==b.p.a)c.rp=getmin(rp,b.lp);
			else if(c.p.b==b.p.b)c.rp=getmin(rp,b.rp);
			else c.rp=getmin(rp,b.p);
		}
		else{
			if(c.p.b==b.p.a)c.rp=getmin(p,b.lp);
			else if(c.p.b==b.p.b)c.rp=getmin(p,b.rp);
			else c.rp=getmin(p,b.p);
		}
		return c;			
	}
};
int n,m,x[30005],y[30005],ans[300005];
int dist(int i,int j){
	return max(abs(x[i]-x[j]),abs(y[i]-y[j]));
}
struct Que{
	int l,r,id;
}q[300005];
vector<int>v[30005],qu[30005];
unordered_map<ll,int>mp[30];int tot[30];vector<int>p[30005];
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	Node c[120005];
	void pushup(int p){
		c[p]=c[ls]+c[rs];
	}
	void build(int l,int r,int p){
		if(l==r){
			c[p].p=(Pair){-1,-1,inf};
			c[p].lp=(Pair){-1,-1,inf};
			c[p].rp=(Pair){-1,-1,inf};			
			return;
		}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x,Pair k){
		if(l==r){
			if(c[p].p.dis>k.dis)swap(c[p].p,k);
			if(c[p].rp.dis>k.dis)swap(c[p].rp,k);
			c[p].lp=(Pair){-1,-1,inf};
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)upd(lson,x,k);
		else upd(rson,x,k);
		pushup(p);
	}
	Node qry(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p];
		int mid=(l+r)>>1;
		if(R<=mid)return qry(lson,L,R);
		if(L>mid)return qry(rson,L,R);
		return qry(lson,L,R)+qry(rson,L,R);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int qry(int l,int r){
	Node tmp=Tr.qry(1,n,1,l,r);
	return max(tmp.lp.dis,tmp.rp.dis);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)x[i]=read(),y[i]=read();
	for(int k=0;(1ll<<k)<=V;k++){
		for(int i=1,id;i<=n;i++){
			if(mp[k].find(gethash(x[i]>>k,y[i]>>k))==mp[k].end())mp[k][gethash(x[i]>>k,y[i]>>k)]=id=++tot[k];
			else id=mp[k][gethash(x[i]>>k,y[i]>>k)];
			for(int dx=-1;dx<=1;dx++)for(int dy=-1;dy<=1;dy++){
				int nx=(x[i]>>k)+dx,ny=(y[i]>>k)+dy;
				if(mp[k].find(gethash(nx,ny))==mp[k].end())continue;
				int nid=mp[k][gethash(nx,ny)];
				for(int o=(int)v[nid].size()-1,c=3;o>=0&&c;o--,c--){
					p[i].push_back(v[nid][o]);
				}
			}
			v[id].push_back(i);
		}
		for(int i=1;i<=tot[k];i++)v[i].clear();
	}
	m=read();
	for(int i=1;i<=m;i++)q[i].l=read(),q[i].r=read(),q[i].id=i,qu[q[i].r].push_back(i);
	Tr.build(1,n,1);
	for(int r=1;r<=n;r++){
		sort(p[r].begin(),p[r].end());p[r].erase(unique(p[r].begin(),p[r].end()),p[r].end());
		for(auto x:p[r])Tr.upd(1,n,1,x,(Pair){x,r,dist(x,r)});
		for(auto x:qu[r])ans[q[x].id]=qry(q[x].l,q[x].r);
	}
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

C0348 【1017 B组】模拟测试

A 【1017 B组】集合

容易发现如果我们固定子区间的左端点 \(l\),那么合法的右端点 \(r\) 一定是从 \(l\) 开始往后连续的一段。记 \(l=i\) 的最大合法右端点为 \(p_i\),发现 \(p_i\) 不降,于是双指针扫过去即可。复杂度 \(\mathcal{O}(n\log 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;
}
int n,m,ans,a[200005],c[200005];
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,ln,rn,sz;
		Node operator +(const Node &y)const{
			Node z;
			z.s=max(s,y.s);
			z.s=max(z.s,rn+y.ln);
			if(ln==sz)z.ln=sz+y.ln;
			else z.ln=ln;
			if(y.rn==y.sz)z.rn=y.sz+rn;
			else z.rn=y.rn;
			z.sz=sz+y.sz;
			return z;
		}
	}c[800005];
	void pushup(int p){
		c[p]=c[ls]+c[rs];
	}
	void build(int l,int r,int p){
		if(l==r){c[p].s=c[p].ln=c[p].rn=0,c[p].sz=1;return;}
		int mid=(l+r)>>1;
		build(lson),build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int x,int k){
		if(l==r){
			c[p].s=c[p].ln=c[p].rn=k;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	Node query(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p];
		int mid=(l+r)>>1;
		if(R<=mid)return query(lson,L,R);
		if(L>mid)return query(rson,L,R);
		return query(lson,L,R)+query(rson,L,R);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Tr.build(1,n,1);
	for(int i=1,j=0;i<=n;i++){
		while(j<=n&&Tr.query(1,n,1,1,n).s<=m){
			j++,c[a[j]]++;
			if(c[a[j]]==1)Tr.update(1,n,1,a[j],1);
		}
		ans+=(j-1)-i+1;c[a[i]]--;
		if(c[a[i]]==0)Tr.update(1,n,1,a[i],0);
	}
	printf("%lld\n",ans);
	return 0;
}

B 【1017 B组】差后队列

感觉就是硬推吧,没什么好说的。

点击查看代码
#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;
}
struct Oper{
	int l,r,c,d,pos;
}b[1000005];
int n,tot,iv[1000005],op[1000005],x[1000005],ans0[1000005],ans1[1000005],tag[1000005];
int s[1000005],L[1000005],R[1000005];
void solve(){
	s[tot+1]=0;for(int i=tot;i>=1;i--)s[i]=(s[i+1]*b[i].d%mod+b[i].c)%mod;
	for(int i=b[tot].l;i<=b[tot].r;i++)R[i]=-inf;
	for(int i=1;i<=tot;i++)R[b[i].pos]=max(R[b[i].pos],i);
	for(int i=b[tot].l,j=1;i<=b[tot].r;i++){
		while(j<=tot&&b[j].r<i)j++;
		if(ans0[i])continue;
		if(R[i]==-inf)ans0[i]=s[j];
		else ans0[i]=s[R[i]+1];
	}
	tot=0;
}
struct Node{
	int op,l,r,s,mx;
}a[1000005];
int getmax(int u,int v){
	return ((x[u]>x[v])?u:v);
}
signed main(){
	n=read();
	iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	for(int i=1;i<=n;i++){
		op[i]=read();
		if(op[i]==0)x[i]=read();
		else x[i]=i;
	}
	int lst=1,m=0,cnt=0,sum=x[1],mx=1;
	for(int i=2;i<=n;i++){
		if(op[i]!=op[i-1])a[++m]=(Node){op[i-1],lst,i-1,sum,mx},sum=x[i],mx=i,lst=i;
		else sum=(sum+x[i])%mod,mx=getmax(mx,i);
	}
	a[++m]=(Node){op[n],lst,n,sum,mx},lst=0,sum=0,mx=0;
	for(int i=1;i<=m;i++){
		if(a[i].op==0){
			cnt+=a[i].r-a[i].l,sum=(sum+a[i].s-x[a[i].mx]+mod)%mod;
			if(mx==0)mx=a[i].mx;
			else cnt++,sum=(sum+min(x[mx],x[a[i].mx]))%mod,mx=getmax(mx,a[i].mx);
		}
		else{
			if(cnt+1==a[i].r-a[i].l+1){
				int P=(cnt-a[i].r+a[i].l)%mod*iv[cnt]%mod;
				b[++tot]=(Oper){lst+1,a[i].r,(a[i].s-a[i].r+mod)%mod*iv[cnt]%mod,P,mx};
				tag[a[i].l]=sum*iv[cnt]%mod;ans1[a[i].r]=x[mx],ans0[mx]=a[i].r;mx=0,lst=a[i].r;sum=0,cnt=0;solve();
			}
			else{
				int P=(cnt-a[i].r+a[i].l-1)%mod*iv[cnt]%mod;
				b[++tot]=(Oper){lst+1,a[i].r,a[i].s*iv[cnt]%mod,P,mx};
				tag[a[i].l]=sum*iv[cnt]%mod;sum=sum*P%mod,cnt-=a[i].r-a[i].l+1;
			}
		}
	}
	solve();
	for(int i=1,now=0;i<=n;i++){
		if(tag[i])now=tag[i];
		if(!ans1[i])ans1[i]=now;
	}
	for(int i=1;i<=n;i++){
		printf("%lld ",((op[i]==0)?ans0[i]:ans1[i]));
	}
	return 0;
}

C 【1017 B组】蛋糕

\(f_{l,r,k}\) 表示考虑 \([l,r]\) 范围内的蛋糕,且所有蛋糕已经从下往上被砍掉了高度为 \(k\) 的一段的最小划分代价。大概长这样:

然后转移分两种:

像这样,要么继续从下往上砍,要么选中间一列单独算,然后往两边递归。但是这样单次转移是 \(\mathcal{O}(n)\) 的。

然后你会发现,第二种转移你只会选中间最高的那列来分割,可以感性理解,因为不选最高的之后还要考虑。于是单次转移就变成 \(\mathcal{O}(1)\) 了。然后你会发现写个记忆化搜索就过了。证明如下:

点击查看代码
#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;
}
int n,a[3005],mx[3005][3005],mn[3005][3005];
ll gethash(int l,int r,int k){
	return 1ll*k*(n+1)*(n+1)+1ll*l*(n+1)+r;
}
ll ask(int l,int r){
	return 1ll*(l+r)*(r-l+1)/2;
}
struct HashTable{
	const int M=999997;
	struct Node{
		int nxt;ll val,key;
	}d[20000005];
	int head[1000005],T;
	int f(ll key){return (key%M+M)%M;}
	int get(ll key){
		for(int u=head[f(key)];u;u=d[u].nxt)if(d[u].key==key)return u;
		return -1;
	}
	void upd(ll key,ll val){
		int pos=get(key);if(pos!=-1){d[pos].val=val;return;}
		d[++T]=(Node){head[f(key)],val,key};head[f(key)]=T;
	}
	ll ask(ll key){
		int pos=get(key);if(pos==-1)return -1;
		return d[pos].val;
	}
}f;
inline ll dfs(int l,int r,int k){
	if(l>r)return 0;
	if(l==r)return a[l]-k;
	ll tmp=f.ask(gethash(l,r,k));if(tmp!=-1)return tmp;
	int p1=mx[l][r],p2=mn[l][r];
	ll res=dfs(l,p1-1,k)+dfs(p1+1,r,k)+a[p1]-k;
	res=min(res,dfs(l,p2-1,a[p2])+dfs(p2+1,r,a[p2])+ask(a[p1]-a[p2]+1,a[p1]-k));
	f.upd(gethash(l,r,k),res);return res;
}
int getmax(int x,int y){
	return ((a[x]>a[y])?x:y);
}
int getmin(int x,int y){
	return ((a[x]<a[y])?x:y);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++){
		mx[i][i]=mn[i][i]=i;
		for(int j=i+1;j<=n;j++){
			mx[i][j]=getmax(mx[i][j-1],j);
			mn[i][j]=getmin(mn[i][j-1],j);
		}
	}
	printf("%lld\n",dfs(1,n,0));
	return 0;
}

D 【1017 B组】字符替换

P8885

C0350 【1019 B组】模拟测试

说是 noip 模拟赛,感觉算是完美复刻了:T2 巨大 ds,T3 简单边双。但是我也完美复刻了死磕 T2 一整场的惨状捏。

A 【1019 B组】出关

定义 \(f_{i}\) 表示凑出 \(s\) 的前 \(i\) 个字符的最小代价。那么有转移

  • \(f_i\gets f_{i-1}+t_{0,c}\)

  • 如果 \(s[1\ldots i-j]=s[j+1\ldots i]\),则 \(f_i\gets f_j+t_1+(2j-i)t_2\)

发现 \(f_j+t_1+(2j-i)t_2=(f_j+2jt_2)+(-it_2+t_1)\),相当于我们要求前面那坨的最小值。同时一个 \(j\) 能贡献的 \(i\) 也是一段区间,于是预处理一下用个数据结构维护一下即可。

还有个 \(\mathcal{O}(n)\) 做法。大概就是你发现 \(f_j+2jt_2\) 似乎是单调不降的,所以你可以单调队列维护一下。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int inf=1e18;
const ull base=131;
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;
}
ull h[1000005],pw[1000005];
ull ask(int l,int r){
	return h[r]-h[l-1]*pw[r-l+1];
}
char c[1000005];int t0[30],len[1000005],f[1000005];vector<int>v[1000005];
priority_queue<int,vector<int>,greater<int> >q,del; 
signed main(){
	scanf("%s",c+1);int n=strlen(c+1);
	for(int i=0;i<26;i++)t0[i]=read();
	int t1=read(),t2=read();
	pw[0]=1ull;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*base;
	for(int i=1;i<=n;i++)h[i]=h[i-1]*base+c[i]; 
	for(int j=1;j<=n;j++){
		int l=1,r=min(j,n-j),res=0;
		while(l<=r){
			int mid=(l+r)>>1;
			if(ask(1,mid)==ask(j+1,j+mid))res=mid,l=mid+1;
			else r=mid-1;
		}
		if(res)len[j]=res,v[j+res].push_back(j);
	}
	f[0]=0;
	for(int i=1;i<=n;i++){
		f[i]=f[i-1]+t0[c[i]-'a'];
		while(!q.empty()&&!del.empty()&&q.top()==del.top())q.pop(),del.pop();
		if(!q.empty())f[i]=min(f[i],q.top()+(t1-i*t2));
		if(len[i])q.push(f[i]+2*i*t2);
		for(auto x:v[i])del.push(f[x]+2*x*t2);
	}
	printf("%lld\n",f[n]);
	return 0;
}

upd:faster solution

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const ull base=131;
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;
}
ull h[10000005],pw[10000005];
ull ask(int l,int r){
	return h[r]-h[l-1]*pw[r-l+1];
}
char c[10000005];int t0[30];ll f[10000005];
signed main(){
	scanf("%s",c+1);int n=strlen(c+1);
	for(int i=0;i<26;i++)t0[i]=read();
	int t1=read(),t2=read();
	pw[0]=1ull;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*base;
	for(int i=1;i<=n;i++)h[i]=h[i-1]*base+c[i]; 
	f[0]=0;
	for(int i=1,j=0;i<=n;i++){
		f[i]=f[i-1]+t0[c[i]-'a'];
		while(j<i&&j*2<i)j++;
		while(j<i&&ask(1,i-j)!=ask(j+1,i))j++;
		if(j<i)f[i]=min(f[i],f[j]+t1+1ll*(2*j-i)*t2);
	}
	printf("%lld\n",f[n]);
	return 0;
}

B 【1019 B组】补天

C 【1019 B组】理水

弱化:CF652E

本场第二简单题,但我没看。

考虑先缩边双,因为一个边双内任意两点间必定存在一条经过边双内指定边的路径,所以如果这个边双内有泄洪出口那么所有点都能幸免于难。然后现在问题就变成了一个点在不经过特殊边的情况下能到达多少点(如果这个点内有特殊边也不行),并查集合并一下即可。

点击查看代码
#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,w,id,nxt;
}e[400005];
int tot=1,head[200005];
void add(int u,int v,int w,int id){
	e[++tot]=(edge){v,w,id,head[u]},head[u]=tot;
}
int cur,dfn[200005],low[200005],B[200005];
void tarjan(int u,int fa){
	dfn[u]=low[u]=++cur;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,id=e[i].id;
		if(!dfn[v]){
			tarjan(v,i);low[u]=min(low[u],low[v]);
			if(dfn[u]<low[v])B[id]=1;
		}
		else if(i!=(fa^1))low[u]=min(low[u],dfn[v]);
	}
}
int vis[200005],bel[200005],cnt[200005],tag[200005];
void sol(int u,int id){
	if(vis[u])return;
	bel[u]=id,cnt[id]++,vis[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		if(w)tag[id]=1;
		sol(v,id);
	}
}
int fa[200005];
int find(int x){
	return ((fa[x]==x)?x:fa[x]=find(fa[x]));
}
void merge(int x,int y){
	if((x=find(x))==(y=find(y)))return;
	fa[x]=y,cnt[y]+=cnt[x];
}
void dfs(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;if(v==fa)continue;
		dfs(v,u);if(!tag[u]&&!tag[v]&&!w)merge(u,v);
	}
}
int n,m,x[200005],y[200005],z[200005];
signed main(){
	n=read(),m=read();
	for(int i=1,u,v,w;i<=m;i++){
		u=read(),v=read(),w=read(),add(u,v,w,i),add(v,u,w,i);
		x[i]=u,y[i]=v,z[i]=w;
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=m;i++)if(!B[i])add(x[i],y[i],z[i],i),add(y[i],x[i],z[i],i);
	int num=0;for(int i=1;i<=n;i++)if(!vis[i])sol(i,++num);
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=m;i++)if(bel[x[i]]!=bel[y[i]])add(bel[x[i]],bel[y[i]],z[i],i),add(bel[y[i]],bel[x[i]],z[i],i);
	for(int i=1;i<=num;i++)fa[i]=i;
	dfs(1,0);for(int i=1;i<=n;i++)printf("%lld ",(tag[bel[i]]?n-1:n-cnt[find(bel[i])]));
	return 0;
}

D 【1019 B组】非攻

推推推!

把这个排列 \(p\)\(i\to p_i\) 这样连边,然后就可以把整个排列划分成若干个置换环。每个大小为 \(S\) 的置换环最少需要交换 \(S-1\) 次,最小代价是环内最小值乘上其他值的和。

然后一个点集为 \(S\) 的置换环有 \((|S|-1)!\) 个,因为你可以固定最小值,剩下的在环上随便填。不用在乘上最小值位置是因为这是一个环,没有位置。然后可以开始推式子了。

\[\begin{aligned} ans&=\sum\limits_{i=1}^{n-1}i\sum\limits_{S\subset (i,n]}|S|!(n-|S|-1)!\sum\limits_{j\in S}j\\ &=\sum\limits_{i=1}^{n-1}i\sum\limits_{j=i+1}^{n}j\sum\limits_{s=1}^{n-i}s!(n-s-1)!\sum\limits_{S\subset (i,n]\land |S|=s\land j\in S}1\\ &=\sum\limits_{i=1}^{n-1}i\dfrac{(n+i+1)(n-i)}{2}\sum\limits_{s=1}^{n-i}s!(n-s-1)!\dbinom{n-i-1}{s-1}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}i(n+i+1)(n-i)\sum\limits_{s=1}^{n-i}\dfrac{s!(n-s-1)!(n-i-1)!}{(s-1)!(n-i-s)!}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}i(n+i+1)(n-i)!\sum\limits_{s=1}^{n-i}\dfrac{s(n-s-1)!}{(n-i-s)!}\\ \end{aligned} \]

把后面的式子设成 \(f(i)\),单独拎出来化简。

\[\begin{aligned} f(i)&=\sum\limits_{s=1}^{n-i}\dfrac{s(n-s-1)!}{(n-i-s)!}\\ &=(i-1)!\sum\limits_{s=1}^{n-i}s\dbinom{n-s-1}{i-1}\\ &=(i-1)!\sum\limits_{s=2}^{n-i+1}(s-1)\dbinom{n-s}{i-1}\\ &=(i-1)!\sum\limits_{s=i-1}^{n-2}(n-s-1)\dbinom{s}{i-1}\\ &=(i-1)!n\sum\limits_{s=i-1}^{n-2}\dbinom{s}{i-1}-(i-1)!\sum\limits_{s=i-1}^{n-2}(s+1)\dbinom{s}{i-1}\\ &=(i-1)!n\sum\limits_{s=i-1}^{n-2}\dbinom{s}{i-1}-(i-1)!i\sum\limits_{s=i-1}^{n-2}\dbinom{s+1}{i}\\ &=(i-1)!n\sum\limits_{s=i-1}^{n-2}\dbinom{s}{i-1}-(i-1)!i\sum\limits_{s=i}^{n-1}\dbinom{s}{i}\\ &=(i-1)!n\dbinom{n-1}{i}-(i-1)!i\dbinom{n}{i+1}\\ &=(i-1)!n\dbinom{n-1}{i}-i!\dbinom{n}{i+1}\\ \end{aligned} \]

注意上述推导中倒数第三步到倒数第二步的变形。此时相当于我们要证明 \(\sum\limits_{i=l}^r\dbinom{i}{l}=\dbinom{r+1}{l+1}\)。考虑你现在有 \(l+1\) 个物品,要把它们放到 \(r+1\) 个位置里。不妨枚举最靠后那个物品的位置 \(i+1\),那么剩下的 \(l\) 个物品就要放进 \(i\) 个位置里了。注意 \(l\) 个物品至少需要 \(l\) 个位置,所以 \(i\ge l\)

然后带回原式:

\[\begin{aligned} ans&=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}i(n+i+1)(n-i)!((i-1)!n\dbinom{n-1}{i}-i!\dbinom{n}{i+1})\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}i(n+i+1)(n-i)!(i-1)!n\dbinom{n-1}{i}-i(n+i+1)(n-i)!i!\dbinom{n}{i+1}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}\dfrac{i(n+i+1)(n-i)!(i-1)!n(n-1)!}{i!(n-i-1)!}-\dfrac{i(n+i+1)(n-i)!i!n!}{(i+1)!(n-i-1)!}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}\dfrac{(n+i+1)(n-i)n(n-1)!}{1}-\dfrac{i(n+i+1)(n-i)n!}{(i+1)}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}(n-i)(n+i+1)\dfrac{n!(i+1)-in!}{(i+1)}\\ &=\dfrac{1}{2}\sum\limits_{i=1}^{n-1}\dfrac{(n-i)(n+i+1)n!}{(i+1)}\\ \end{aligned} \]

做完啦!复杂度 \(\mathcal{O}(n)\)。还可以进一步优化,但我不会了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,i2=(mod+1)/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 iv[10000005],jc[10000005];
signed main(){
	int n=read(),ans=0;
	jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	for(int i=1;i<=n-1;i++)ans=(ans+(n+i+1)*(n-i)%mod*jc[n]%mod*iv[i+1]%mod)%mod;
	printf("%lld\n",ans*i2%mod);
	return 0;
}

C0352 【1020 B组】模拟测试

A 【1020 B组】草莓列车

牛牛题。这么多区间取 \(\max\) 操作,但是只有最后一次询问,有什么办法可以 \(\mathcal{O}(1)\) 修改吗?

发现这个操作跟 ST 表很像啊,但是反了过来。怎么搞呢?使用类似 P3295 的思想,把 ST 表建出来,然后每个操作就只涉及 2 个点了!于是做完了,复杂度 \(\mathcal{O}(m+n\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;
}
namespace Maker{
	unsigned int x0,seed;
	void init() {scanf("%u%u",&x0,&seed);}
	inline unsigned int getnum(){
		x0=(x0<<3)^x0;
		x0=((x0>>5)+seed)^x0;
		return x0;
	}
}
int Log[100005];unsigned int a[100005],f[20][100005];
signed main(){
	int n,m,typ;
	scanf("%d%d%d",&n,&m,&typ);
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	for(int j=0;j<=Log[n];j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=0;
		}
	}
	for(int i=1;i<=n;i++)a[i]=read();
	Maker::init();
	for(int i=1; i<=m; ++i){
		int l=Maker::getnum()%n+1,r=Maker::getnum()%n+1;
		unsigned int v=Maker::getnum();
		if(l>r) swap(l,r);
		if(typ==1) l=1;
		int o=Log[r-l+1];
		f[o][l]=max(f[o][l],v),f[o][r-(1<<o)+1]=max(f[o][r-(1<<o)+1],v);
	}
	for(int j=Log[n];j>=1;j--){
		for(int i=1;i+(1<<j)-1<=n;i++){
			f[j-1][i]=max(f[j-1][i],f[j][i]);
			f[j-1][i+(1<<(j-1))]=max(f[j-1][i+(1<<(j-1))],f[j][i]);
		}
	}
	for(int i=1;i<=n;i++)printf("%u ",max(f[0][i],a[i]));
	return 0;
}

C0353 【1023 B组】模拟测试

A 【1023 B组】道路

容易发现从 \((s_x,s_y)\) 走到 \((t_x,t_y)\) 的过程中我们不会拐太多次弯,最多也就拐两次。

点击查看代码
#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 a[200005],b[200005];
signed main(){
	int n=read(),m=read(),q=read(),mnx=inf,mny=inf; 
	for(int i=1;i<=n;i++)a[i]=read(),mnx=min(mnx,a[i]);
	for(int i=1;i<=m;i++)b[i]=read(),mny=min(mny,b[i]);
	while(q--){
		int sx=read(),sy=read(),tx=read(),ty=read(),ans=inf;
		//1 step
		{
			if(sx==tx)ans=min(ans,a[sx]);
			if(sy==ty)ans=min(ans,b[sy]);
		}
		//2 step
		{
			ans=min(ans,max(a[sx],b[ty]));
			ans=min(ans,max(b[sy],a[tx]));
		}
		//3 step
		{
			ans=min(ans,max({a[sx],a[tx],mny}));
			ans=min(ans,max({b[sy],b[ty],mnx}));
		}
		printf("%lld\n",ans);
	}
	return 0;
}

B 【1023 B组】烟花

首先度数 \(\le 2\) 就是一条链,那么问题就是在 \(u,v\) 开头的所有链中选不超过 \(m\) 条。在一颗大小为 \(s\) 的子树内,选一条链的方案有 \(s\) 种,不选的方案有一种,于是可以先预处理出在以 \(u\) 为根的子树内选 \(m\) 条链的方案数,这就是树形 dp。然后这个又可以很方便的删去一个子树的贡献,然后做完了。

点击查看代码
#include<bits/stdc++.h>
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;
}
struct edge{
	int v,id,nxt;
}e[600005];
int tot,head[300005];
void add(int u,int v,int id){
	e[++tot]=(edge){v,id,head[u]},head[u]=tot;
}
int n,m,siz[300005],f[300005][55],g[55],h[55],s[300005][55];
void dfs1(int u,int fa){
	siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u);siz[u]+=siz[v];
	}
	f[u][0]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		for(int j=0;j<=m;j++)g[j]=f[u][j];
		for(int j=1;j<=m;j++)g[j]=(g[j]+1ll*f[u][j-1]*siz[v]%mod)%mod;
		for(int j=0;j<=m;j++)f[u][j]=g[j];
	}
	s[u][0]=f[u][0];
	for(int j=1;j<=m;j++)s[u][j]=(s[u][j-1]+f[u][j])%mod;
}
int ans[300005];
void dfs2(int u,int fa){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,id=e[i].id;if(v==fa)continue;
		for(int j=0;j<=m;j++)g[j]=f[u][j];
		for(int j=1;j<=m;j++)g[j]=(g[j]-1ll*g[j-1]*siz[v]%mod+mod)%mod;
		for(int j=0;j<=m;j++)h[j]=g[j];
		for(int j=1;j<=m;j++)h[j]=(h[j]+1ll*g[j-1]*(n-siz[u])%mod)%mod;
		for(int j=0;j<=m;j++)ans[id]=(ans[id]+1ll*h[j]*s[v][m-j]%mod)%mod;
		dfs2(v,u);
	}
}
signed main(){
	n=read(),m=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v,i),add(v,u,i);
	dfs1(1,0);dfs2(1,0);
	for(int i=1;i<n;i++)printf("%d\n",ans[i]);
	return 0;
}

C 【1023 B组】彩排

不像人能想到的题。

70pts 很简单,你发现这个过程类似于冒泡排序,相当于从 \(a\) 开始排,每次能交换相邻两项 \(i,i+1\),且每轮 \(i\) 的奇偶性交替。这样做显然下标差不会低于 \(n-1\)

但是正解跟这个关系不大。方便起见我们把答案序列和 \(a\) 都颠倒一下。记一个变量 \(x\) 表示从当前位置往前找 \(n-1\) 个数(不包括当前位置),在 \(n\) 个数中缺的那个。可知 \(x\) 初始为 \(a_1\)。发现对于 \(i>n\),要么 \(b_i=b_{i-n+1}\),要么 \(b_i=x,x=b_{i-n+1}\)。现在把答案序列分成长为 \(n-1\) 的组,每一组看做在上一组的基础上进行修改,那么两种选择变成要么不变,要么 \(\text{swap}(b_i,x)\)。对于每个位置 \(i\),它们的最终目标都是确定的,为 \(n-i+1\),我们记做 \(c_i\)。下面给出一个贪心的构造方式:若扫的过程中有 \(c_i=x\)\(b_i\neq c_i\land x=1\),则交换。这样显然是有正确性的。

证一下为什么答案在 \(\dfrac{n^2}{2}\) 级别。首先在随机意义下,第二种操作不多,我们可以认为它不存在。然后发现对于一个大小为 \(m\) 的置换环,考虑每个点到终点的过程,在随机意义下这个距离大约为 \(\dfrac{m}{2}\),得证。

点击查看代码
#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 a[1005],b[1005],c[1005];
int check(int n){
	for(int i=1;i<n;i++)if(b[i]!=c[i])return 0;
	return 1;
}
signed main(){
	int n=read();vector<int>ans;
	for(int i=1;i<=n;i++)a[i]=read();
	reverse(a+1,a+n+1);
	for(int i=2;i<=n;i++)b[i-1]=a[i];
	for(int i=1;i<n;i++)c[i]=n-i+1;
	ans.push_back(a[1]);int x=a[1];
	for(int i=1;i<n;i++)ans.push_back(b[i]);
	while(!check(n)){
		for(int i=1;i<n;i++){
			if(x==c[i])swap(x,b[i]);
			else if(x==1&&b[i]!=c[i])swap(x,b[i]);
			ans.push_back(b[i]);
		}
	}
	ans.push_back(1);reverse(ans.begin(),ans.end());
	printf("%lld\n",(int)ans.size());
	for(auto x:ans)printf("%lld ",x);
	return 0;
}

D 【1023 B组】树上的数

好厉害。

先读一遍,感觉这个相邻的要有更小值的限制是最难做的,想想看有什么办法把它搞掉。发现这个过程等价于依次安排边权为 \(2\to n\) 的边的位置,且每次放的边必须跟以前的一条边相邻。然后这个极差怎么更形象地表示?可以看成每个点在周围有边时被点亮,在周围所有边都被安排完后熄灭,最小化每个点点亮时间之和。

然后你发现最优策略一定是一次安排完整个子树后再考虑下一棵,所以一棵子树内的所有边的边权是连续的。然后思考这个安排的最优策略,发现它一定是最后安排一个点的重儿子。具体结论我不太会证,感性理解一下就好?

然后就可以推出一个点的贡献就是 \(siz(u)-siz(son(u))\),特别的,叶子贡献为 0。于是维护一下随便删一条边之后的这个东西即可。

最后,还有那个跟 1 号边在一个连通块内的限制。这个很简单啊,可以新建一个根节点表示 1 号边,边点转化一下。发现这样会让答案增加 2,减去即可。原因可以分类讨论。

点击查看代码
#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,id,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v,int id){
	e[++tot]=(edge){v,id,head[u]},head[u]=tot;
}
int n,sum[500005],siz[500005],son[2][500005],tag[500005];
void dfs1(int u,int fa){
	siz[u]=1,sum[u]=0;son[0][u]=son[1][u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u);siz[u]+=siz[v];sum[u]+=sum[v];
		if(siz[v]>siz[son[0][u]])son[1][u]=son[0][u],son[0][u]=v;
		else if(siz[v]>siz[son[1][u]])son[1][u]=v;
	}
	if(son[0][u])tag[son[0][u]]=1;
	if(son[1][u])tag[son[1][u]]=2;
	if(son[0][u])sum[u]+=siz[u]-siz[son[0][u]];
}
struct BIT{
	int c[500005];
	void add(int x,int v){
		for(x++;x<=n+1;x+=x&-x)c[x]+=v;
	}
	void add(int l,int r,int v){
		if(l>r)return;
		add(l,v),add(r+1,-v);
	} 
	int ask(int x){
		int res=0;
		for(x++;x;x-=x&-x)res+=c[x];
		return res;
	}
}Tr1,Tr2;
int ans[500005],eu[500005],ev[500005];
void dfs2(int u,int fa,int cnt){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,id=e[i].id;if(v==fa)continue;
		if(son[0][u]==v){
			if(son[1][u]){
				Tr1.add(siz[son[0][u]]-siz[son[1][u]],n,siz[son[0][u]]-siz[son[1][u]]);
				Tr2.add(0,siz[son[0][u]]-siz[son[1][u]]-1,1);					
			}
			else Tr2.add(0,siz[son[0][u]],1);		
		}
		ans[id]=sum[n+1]+Tr1.ask(siz[v])+Tr2.ask(siz[v])*siz[v]-cnt*siz[v]-sum[v];
		if(!son[1][u])ans[id]--;
		dfs2(v,u,cnt+1);
		if(son[0][u]==v){
			if(son[1][u]){
				Tr1.add(siz[son[0][u]]-siz[son[1][u]],n,-siz[son[0][u]]+siz[son[1][u]]);
				Tr2.add(0,siz[son[0][u]]-siz[son[1][u]]-1,-1);					
			}
			else Tr2.add(0,siz[son[0][u]],-1);		
		}
	}
}
signed main(){
	n=read();for(int i=1;i<n;i++)eu[i]=read(),ev[i]=read();
	add(eu[1],n+1,0),add(n+1,eu[1],0);add(ev[1],n+1,0),add(n+1,ev[1],0);
	for(int i=2;i<n;i++)add(eu[i],ev[i],i),add(ev[i],eu[i],i);
	dfs1(n+1,0);dfs2(n+1,0,1);
	for(int i=2;i<n;i++)printf("%lld\n",ans[i]-2);
	return 0;
}

C0354 【1024 B组】模拟测试

难度大概是 B<C<A<D?只会 B 输麻了。

A 【1024 B组】赢钱

好难的 T1 啊。

定义 \(f_i\) 表示初始有 \(i\) 块钱,要赢到不小于 \(b\) 的概率。那么 \(f_0=0,f_b=1\),且 \(f_i=\max\limits_{j\le i}\{pf_{i+j}+(1-p)f_{i-j}\}\)。那么 \(f_{b/2}\) 是多少?应该是全部梭哈,\(f_{b/2}=p\)。为什么?我们感性理解一下:如果我们全部押上,那么赢了就刚好能完成任务。如果分成好几次去赌,那么就更可能失败了啊()

反正此时就有 \(f_{b/2}=p\) 了。同时,显然 \(f_i\) 随着 \(i\) 增大而增大,于是当 \(\dfrac{a}{b}<2\) 时那么 \(f_a<f_{b/2},f_a<p\),否则 \(f_a\ge p\),于是就可以递归的算了。

发现这个东西就是 \(\dfrac{a}{b}\) 的二进制表示。重新定义 \(f_i\) 表示考虑第 \(i\) 位之后的,能玩到向第 \(i-1\) 位进位的概率。当第 \(i\) 位为 0 则 \(f_i=pf_{i+1}\),否则 \(f_i=f_{i+1}+p(1-f_{i+1})\)。但是会有循环小数,怎么办?可以算出系数,然后就是解一个方程了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6,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;res%=mod;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
}
int gcd(int x,int y){
	return ((!y)?x:gcd(y,x%y));
}
int vis[1000005],p[1000005];
signed main(){
	int a=read(),b=read(),q=read(),P=q*qpow(N,mod-2)%mod,Q=(1+mod-P)%mod;
	int g=gcd(a,b),cnt=0;a/=g,b/=g;
	while(!vis[a])vis[a]=++cnt,p[cnt]=(a*2>=b),a=a*2%b;
	int kv=1,bv=0;
	for(int i=cnt;i>=vis[a];i--){
		if(p[i]==0)kv=kv*P%mod,bv=bv*P%mod;
		else kv=kv*Q%mod,bv=(bv*Q%mod+P)%mod;
	}
	int f=bv*qpow(1+mod-kv,mod-2)%mod;
	for(int i=vis[a]-1;i>=1;i--){
		if(p[i]==0)f=f*P%mod;
		else f=(f*Q%mod+P)%mod;
	}
	printf("%lld\n",f);
	return 0;
}

B 【1024 B组】排列

自然的,我们考虑把每个 \((x,y)\) 看成一条无向边,然后会发现当一个点度数大于 2 时就无法满足限制了,此时无解(自环算两个度数)。那么现在图就变成了若干个环和若干条链。下面根据形状分个类:

  • 自环。那么这个点的答案唯一确定,我们当它不存在即可。

  • 至少两个点的环。此时环上每个位置都只能填环上有的数,答案乘 2。

  • 没有连边的点。没有限制,个数记作 \(c_3\)

  • 至少三个点的链。有两种方案,且最后都会剩一个位置可以随便填。个数记作 \(c_1\)

  • 两个点的链。跟上面不同的地方在于当 \(p_x=y,p_y=x\) 的情况会被算重,所以我们要单独考虑,个数记作 \(c_2\)

发现最后一种情况相当于乘上一个 -1 的系数,考虑容斥。枚举有 \(i\) 个两点链这样填,答案为 \(\sum\limits_{i=0}^{c_2}(-1)^i\dbinom{c_2}{i}\times 2^{c_2-i}(c_2-i+c_3+c_1)!\)。别忘了剩下的那些 2。

点击查看代码
#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 pw[500005],jc[500005],iv[500005],ij[500005];
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;
}
struct edge{
	int v,nxt;
}e[1000005];
int tot,head[500005],deg[500005];
void add(int u,int v){
	e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int cnt,num,vis[500005];
void dfs(int u){
	if(vis[u])return;
	vis[u]=1,cnt++,num+=(deg[u]==2);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		dfs(v);
	}
}
unordered_map<int,int>mp[500005];
signed main(){
	int n=read(),m=read();
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=pw[i-1]*2%mod;
	jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
	iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
	ij[0]=1;for(int i=1;i<=n;i++)ij[i]=ij[i-1]*iv[i]%mod;
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read();if(mp[u].count(v)||mp[v].count(u))continue;
		add(u,v),add(v,u);mp[u][v]=mp[v][u]=1;
	}
	for(int i=1;i<=n;i++){
		if(deg[i]>=3)return puts("0"),0;
	}
	int ans=0,p=0,c1=0,c2=0,c3=0;
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		cnt=num=0,dfs(i);
		if(cnt==num){
			if(cnt>=3)p++;
		}
		else{
			if(cnt>2)c1++;
			else if(cnt==2)c2++;
			else c3++;
		}
	}
	for(int i=0;i<=c2;i++){
		if(i&1ll)ans=(ans-pw[c2-i]*jc[c2-i+c3+c1]%mod*C(c2,i)%mod+mod)%mod;
		else ans=(ans+pw[c2-i]*jc[c2-i+c3+c1]%mod*C(c2,i)%mod)%mod;
	}
	printf("%lld\n",ans*pw[p+c1]%mod);
	return 0;
}
/*
7 2
6 6
4 1

216
*/

C 【1024 B组】箱子

由于每个颜色段互不影响,我们分开考虑,答案加和即可。由于这个题神秘的代价计算方法,发现最终答案只与每个点会成为多少次左/右端点有关。以左端点为例,\(a_i\) 会成为左端点当且仅当 \(a_{i-1}\le a_i\),否则你可以继续往左边拓展,于是次数就是 \(\max(0,a_i-a_{i-1})\)。类似的,也可以得到成为右端点的次数为 \(\max(0,a_i-a_{i+1})\)。注意在计算最左边和最右边时需要把超出范围的当成 0。于是线段树维护做完了。

其实挺自然的,还是没认真想啊。

点击查看代码
#include<bits/stdc++.h>
#define int __int128
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;
}
inline void print(int x){
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
int a[200005],C[200005],w[200005];
struct Node{
	int l,r,lc,rc,s,ans;
	Node operator +(const Node &b)const{
		Node c;c.lc=lc,c.rc=b.rc;c.l=l,c.r=b.r;c.ans=ans+b.ans;c.s=s+b.s;
		c.s-=a[r]*w[r]+a[b.l]*w[b.l];
		c.s+=max((int)0,a[r]-a[r+1])*w[r];
		c.s+=max((int)0,a[b.l]-a[b.l-1])*w[b.l];		
		if(rc==b.lc){
			c.ans-=a[r]*w[r]+a[b.l]*w[b.l];
			c.ans+=max((int)0,a[r]-a[r+1])*w[r];
			c.ans+=max((int)0,a[b.l]-a[b.l-1])*w[b.l];
		}
		return c;
	}
};
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct seg{
		Node v;int tag;
	}c[800005];
	void pushup(int p){
		c[p].v=c[ls].v+c[rs].v;
	}
	void pushdown(int p){
		if(!c[p].tag)return;
		c[ls].v.lc=c[ls].v.rc=c[p].tag;
		c[rs].v.lc=c[rs].v.rc=c[p].tag;
		c[ls].v.ans=c[ls].v.s,c[rs].v.ans=c[rs].v.s;
		c[ls].tag=c[rs].tag=c[p].tag;
		c[p].tag=0;
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){
			c[p].v=(Node){l,r,C[l],C[r],2*a[l]*w[l],2*a[l]*w[l]};
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x){
		if(l==r){
			c[p].v=(Node){l,r,c[p].v.lc,c[p].v.rc,2*a[l]*w[l],2*a[l]*w[l]};
			return;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(x<=mid)upd(lson,x);
		else upd(rson,x);
		pushup(p);
	}
	void cov(int l,int r,int p,int L,int R,int v){
		if(L<=l&&r<=R){
			c[p].tag=v;c[p].v.lc=c[p].v.rc=v;
			c[p].v.ans=c[p].v.s;
			return;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(L<=mid)cov(lson,L,R,v);
		if(R>mid)cov(rson,L,R,v);
		pushup(p);
	}
	Node qry(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R){
			return c[p].v;
		}
		int mid=(l+r)>>1;pushdown(p);
		if(R<=mid)return qry(lson,L,R);
		if(L>mid)return qry(rson,L,R);
		return qry(lson,L,R)+qry(rson,L,R);		
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
signed main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read(),C[i]=read(),w[i]=read();
	Tr.build(1,n,1);
	while(m--){
		int op=read();
		if(op==1){
			int x=read(),v1=read(),v2=read();
			a[x]=v1,w[x]=v2;Tr.upd(1,n,1,x);
		}
		else if(op==2){
			int l=read(),r=read(),v=read();
			Tr.cov(1,n,1,l,r,v);
		}
		else{
			int l=read(),r=read();
			print(Tr.qry(1,n,1,l,r).ans);puts("");
		}
	}
	return 0;
}

D 【1024 B组】扌非 歹刂

agc038f

先找出 \(a,b\) 中的所有置换环。随便拎一个出来,不失一般性的记作 \(\{1,2\ldots k\}\),考虑 \(c_1\) 填什么。如果 \(c_1=1\),那么 \(c_k\) 只能选 \(k\),于是 \(c_i=i\);否则 \(c_k=1\)\(c_i=i\bmod k+1\)。即一个置换环上要么不动要么全部转一圈。

考虑每个位置 \(i\) 对答案的影响。下面分个类:

  • \(a_i=b_i=i\):此时不管怎么选都有 \(c_i=d_i\),可以直接让答案 +1,然后不管;

  • \(a_i=i,b_i\neq i\):当 \(b_i\) 所在置换环不动时会产生 1 的贡献;

  • \(a_i\neq i,b_i=i\):当 \(a_i\) 所在置换环不动时会产生 1 的贡献;

  • \(a_i\neq i,b_i\neq i,a_i\neq b_i\):当 \(a_i,b_i\) 所在置换环都不动时会产生 1 的贡献;

  • \(a_i\neq i,b_i\neq i,a_i=b_i\):当 \(a_i,b_i\) 所在置换环都动/都不动时会产生 1 的贡献。

发现如果建立两个集合 \(A,B\) 分别表示不转/转,是否产生贡献就跟两个环在 \(A,B\) 中的关系有关。考虑建立网络流模型。具体地,\(a_i\) 不转当且仅当其与 \(S\) 联通,\(b_i\) 不转当且仅当其与 \(T\) 联通。于是上面的代价可以看做:

  • \(a_i=i,b_i\neq i\)\(S\)\(b_i\) 所在置换环连一条流量为 1 的边;

  • \(a_i\neq i,b_i=i\)\(a_i\) 所在置换环向 \(T\) 连一条流量为 1 的边;

  • \(a_i\neq i,b_i\neq i,a_i\neq b_i\)\(a_i\) 所在置换环向 \(b_i\) 所在置换环连一条流量为 1 的边;

  • \(a_i\neq i,b_i\neq i,a_i=b_i\)\(a_i\) 所在置换环和 \(b_i\) 所在置换环互相连一条流量为 1 的边;

于是问题转化为求最小割。由于这是个二分图,可以 \(\mathcal{O}(m\sqrt{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,w,nxt;
}e[400005];
int tot=1,head[200005],now[200005];
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[200005],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 a[100005],b[100005],va[100005],vb[100005];
signed main(){
	int n=read(),cnt=0,ans=0;
	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++){
		if(va[i]||i==a[i])continue;
		int x=a[i];va[i]=++cnt;
		while(x!=i)va[x]=cnt,x=a[x];
	}
	for(int i=1;i<=n;i++){
		if(vb[i]||i==b[i])continue;
		int x=b[i];vb[i]=++cnt;
		while(x!=i)vb[x]=cnt,x=b[x];
	}
	S=++cnt;T=++cnt;
	for(int i=1;i<=n;i++){
		if(a[i]==i&&b[i]==i){ans++;continue;}
		if(a[i]==i&&b[i]!=i)adde(S,vb[i],1);
		else if(a[i]!=i&&b[i]==i)adde(va[i],T,1);
		else if(a[i]!=b[i])adde(va[i],vb[i],1);
		else adde(va[i],vb[i],1),adde(vb[i],va[i],1);
	}
	while(bfs())ans+=dinic(S,inf);
	printf("%lld",ans);
	return 0;
}

C0357 【1026 B组】模拟测试

题目的质量不做评价。

A 【1026 B组】下棋

在 cwoi 上数据比较弱,各种错误做法都能通过,可以在加强版上交一下。

巨大分类讨论。没啥营养,放个代码得了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define y1 y3456
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 dist(int x1,int y1,int x2,int y2){
	return max(abs(x1-x2),abs(y1-y2));
}
int n,m,x1,y1,x2,y2;
void solve(){
	if(abs(x1-x2)+abs(y1-y2)==1)return puts("-1"),void();
	if(n==3&&m==3){
		if(x2==2&&y2==2)return puts("1"),void();
		else if(x1==2&&y1==2)return puts("3"),void();
		else return puts("2"),void();
	}
	if(n==1||m==1)return puts("2"),void();
	if(n==2&&m==2)return puts("1"),void();
	if(min(n,m)<=2){
		if(n>m)swap(n,m),swap(x1,y1),swap(x2,y2);
		if(m==3&&y2==2)return puts("1"),void();
		else if(abs(y1-y2)>1)return puts("2"),void(); 
		else if(y2<=2||y2>=m-1)return puts("2"),void();
		else return puts("3"),void();
	}
	if(min({dist(x2,y2,1,1),dist(x2,y2,1,m),dist(x2,y2,n,1),dist(x2,y2,n,m)})<=1){
		if(dist(x1,y1,x2,y2)>=2)return puts("2"),void();
		else if(x1==1||y1==1||x1==n||y1==m)return puts("2"),void();
		else return puts("3"),void();
	}
	puts("3");
}
signed main(){
	while(scanf("%lld%lld%lld%lld%lld%lld",&n,&m,&x1,&y1,&x2,&y2)!=EOF){
		solve();
	}
	return 0;
}

B 【1026 B组】鬼渊

提取出题目中的两个限制:联通和没有空腔。空腔的限制是简单的,我们找到每个 0 的极大联通块,容易发现不完全包含这个连通块和不完全包含能完全包裹住它的最小矩形是等价的。记这些矩形为左上角为 \((u,l)\),右下角为 \((d,r)\),定义 \(lim_{i,j,k}\) 表示 \(u,d\in[i,j],r\le k\) 的所有矩形中最大的 \(l\),那么在枚举答案时只要满足左端点大于 \(lim_{i,j,k}\) 就不会包含空腔。怎么转移?如果 \(lim_{i,j,k}=\max\{lim_{i,j-1,k},lim_{i+1,j,k},lim_{i,j,k-1}\}\),此时可能算漏 \(u=i,d=j,r=k\) 的情况,特判一下。

联通的限制需要一点结论。如果我们在任意相邻黑点间连边,会得到一张平面图,而根据平面图欧拉公式有 \(V+F-E=2\),所以我们只需要数 \(V+F-E=2\) 的矩阵有多少个。\(V\)\(E\) 是简单的,因为题目中要求没有空腔,所以 \(F\) 只会存在于 \(2\times 2\) 的 4 个 1 中,再加上最外面的面。容易发现这是互不影响的,枚举 \(i,j\),做前缀和即可。复杂度 \(\mathcal{O}(n^3)\)

欧拉公式的证明也是简单的。考虑初始有一个点时 \(V=F=1,E=0\)。然后每次你可以加一个点一条边,或者在两个已经联通的点之间连边。前者会让 \(V\)\(E\) 同时 +1,而后者会让 \(F\)\(E\) 同时 +1。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define y1 y3456
using namespace std;
const int T=180000;
const int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
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,a[305][305],vis[305][305],U[90005],D[90005],L[90005],R[90005];ll ans;
int in(int x,int y){
	return (x>=1&&x<=n&&y>=1&&y<=m);
}
void dfs(int id,int x,int y){
	vis[x][y]=1;U[id]=min(U[id],x),D[id]=max(D[id],x);L[id]=min(L[id],y),R[id]=max(R[id],y);
	for(int i=0;i<4;i++){
		int nx=x+dx[i],ny=y+dy[i];
		if(in(nx,ny)&&!vis[nx][ny]&&!a[nx][ny])dfs(id,nx,ny);
	}
}
int v[305][305],e1[305][305],e2[305][305],f[305][305],lim[305][305][305];
int sv[305],se1[305],se2[305],sf[305],B[360005];
ll solve(int l,int r){
	for(int i=1;i<=m;i++){
		sv[i]=sv[i-1]+(v[r][i]-v[l-1][i]);
		se1[i]=se1[i-1]+(e1[r][i]-e1[l][i]);
		se2[i]=se2[i-1]+(e2[r][i]-e2[l-1][i]);
		sf[i]=sf[i-1]+(f[r][i]-f[l][i]);
	}
	B[sv[0]-se1[0]-se2[1]+sf[1]+T]++;ll res=0,j=0;
	for(int i=1;i<=m;i++){
		while(j<lim[l][r][i]-1)B[sv[j]-se1[j]-se2[j+1]+sf[j+1]+T]--,j++;
		res+=B[sv[i]-se1[i]-se2[i]+sf[i]-1+T],B[sv[i]-se1[i]-se2[i+1]+sf[i+1]+T]++;
	}
	while(j<=m)B[sv[j]-se1[j]-se2[j+1]+sf[j+1]+T]--,j++;
	return res;
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%1d",&a[i][j]);
	int num=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!vis[i][j]&&!a[i][j]){
		num++,U[num]=D[num]=i,L[num]=R[num]=j;dfs(num,i,j);
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			for(int k=1;k<=m;k++){
				lim[i][j][k]=1;
			}
		}
	}
	for(int i=1;i<=num;i++){
		lim[U[i]-1][D[i]+1][R[i]+1]=L[i];
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=1;k<=m;k++){
				lim[i][j][k]=max(lim[i][j][k],lim[i+1][j][k]);
				lim[i][j][k]=max(lim[i][j][k],lim[i][j-1][k]);
				if(k-1>=1)lim[i][j][k]=max(lim[i][j][k],lim[i][j][k-1]);
			}
		}
	}
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++){
			v[i][j]=v[i-1][j]+a[i][j];
			e1[i][j]=e1[i-1][j]+(a[i-1][j]&a[i][j]);
			e2[i][j]=e2[i-1][j]+(a[i][j-1]&a[i][j]);
			f[i][j]=f[i-1][j]+(a[i-1][j-1]&a[i-1][j]&a[i][j-1]&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			ans+=solve(i,j);
		}
	} 
	printf("%lld\n",ans); 
	return 0;
}

C 【1026 B组】装饰

简单题。发现正着做这个限制有点复杂,但不满足限制就相当于路径上所有边颜色相同,考虑容斥。枚举不合法的限制,并查集合并,复杂度 \(\mathcal{O}(2^mnm)\)

点击查看代码
#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 fa[65];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void merge(int x,int y){
	if((x=find(x))==(y=find(y)))return;
	fa[y]=x;
}
struct edge{
	int v,id,nxt;
}e[125];
int head[65],tot;
void add(int u,int v,int id){
	e[++tot]=(edge){v,id,head[u]},head[u]=tot;
}
int n,m,k,cur,dfn[65],Log[65],f[8][65],Fa[65],val[65];
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,Fa[u]=fa;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,id=e[i].id;if(v==fa)continue;
		val[v]=id;dfs(v,u);
	}
}
void init(){
	Log[1]=0;for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	dfs(1,0);
	for(int j=1;j<=Log[n];j++){
		for(int i=1;i+(1ll<<j)-1<=n;i++){
			f[j][i]=getmin(f[j-1][i],f[j-1][i+(1ll<<(j-1))]);
		}
	}
}
int getlca(int u,int v){
	if(u==v)return u;
	if((u=dfn[u])>(v=dfn[v]))swap(u,v);
	int o=Log[v-u++];
	return getmin(f[o][u],f[o][v-(1ll<<o)+1]);
}
void solve(int u,int v){
	int lca=getlca(u,v);vector<int>tmp;
	while(u!=lca)tmp.push_back(val[u]),u=Fa[u];
	while(v!=lca)tmp.push_back(val[v]),v=Fa[v];	
	for(int i=1;i<(int)tmp.size();i++)merge(tmp[i-1],tmp[i]);
}
int a[20],b[20];
signed main(){
	n=read(),m=read(),k=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v,i),add(v,u,i);
	init();
	for(int i=1;i<=m;i++)a[i]=read(),b[i]=read();
	for(int i=1;i<=m;i++){
		if(a[i]==b[i]||Fa[a[i]]==b[i]||Fa[b[i]]==a[i])return puts("0"),0;
	}
	int ans=0;
	for(int msk=0;msk<(1ll<<m);msk++){
		int o=1,res=1;
		for(int i=1;i<n;i++)fa[i]=i;
		for(int i=0;i<m;i++)if((msk>>i)&1ll)solve(a[i+1],b[i+1]),o*=-1;
		for(int i=1;i<n;i++)if(find(i)==i)res=res*k%mod;
		ans=(ans+res*o%mod+mod)%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

D 【1026 B组】挖矿

神秘题。相似题 P4898

条件相当于枚举 \([l,r]\),在点亮 \(a_{i,j}\in[l,r]\) 的点后恰好能构成一个矩形。不妨观察矩形的性质,考虑 \(a\) 中的所有 \(2\times 2\) 的小矩形(超出边界也算)。记 \(c_k\) 表示内部点亮点数为 \(k\) 的小矩形个数,容易发现 \(c_1+c_3\ge 4\),且 \(c_1+c_3=4\) 当且仅当点亮图形构成矩形。于是枚举 \(r\),线段树维护 \(l\in [L,R]\)\(c_1+c_3\) 的最小值和最小值个数。点亮一个新点时只会影响周围四个小矩形,直接修改即可。复杂度 \(\mathcal{O}(nm\log nm)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
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 Node{
	int mi,s;
	Node operator +(const Node &b)const{
		Node c;
		c.mi=min(mi,b.mi),c.s=0;
		if(mi==c.mi)c.s+=s;
		if(b.mi==c.mi)c.s+=b.s;
		return c;
	}
};
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct seg{
		Node v;int tag;
	}c[800005];
	void pushup(int p){	 
		c[p].v=c[ls].v+c[rs].v;
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		c[ls].v.mi+=c[p].tag,c[rs].v.mi+=c[p].tag;
		c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
		c[p].tag=0;  
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){
			c[p].v=(Node){0,1};
			return;
		}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void upd(int l,int r,int p,int x,int k){
		if(l==r){
			c[p].v=(Node){k,1};
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(x<=mid)upd(lson,x,k);
		else upd(rson,x,k);
		pushup(p);
	}
	void add(int l,int r,int p,int L,int R,int k){
		if(L<=l&&r<=R){
			c[p].tag+=k,c[p].v.mi+=k;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(lson,L,R,k);
		if(R>mid)add(rson,L,R,k);
		pushup(p);
	}
	Node qry(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].v;
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(R<=mid)return qry(lson,L,R);
		if(L>mid)return qry(rson,L,R);
		return qry(lson,L,R)+qry(rson,L,R);
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
int n,m;ll ans;vector<int>a[200005];pii b[200005];
int in(int x,int y){
	return (x>=1&&x<=n&&y>=1&&y<=m);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i].resize(m+5);
		for(int j=1;j<=m;j++)a[i][j]=read(),b[a[i][j]]=mk(i,j);
	}
	Tr.build(1,n*m,1);
	for(int r=1;r<=n*m;r++){
		int i=b[r].fi,j=b[r].se;
		{
			vector<int>tmp;
			if(in(i-1,j-1)&&a[i-1][j-1]<=r)tmp.push_back(a[i-1][j-1]);
			if(in(i-1,j)&&a[i-1][j]<=r)tmp.push_back(a[i-1][j]);
			if(in(i,j-1)&&a[i][j-1]<=r)tmp.push_back(a[i][j-1]);
			if(in(i,j)&&a[i][j]<=r)tmp.push_back(a[i][j]);
			sort(tmp.begin(),tmp.end());int siz=(int)tmp.size();
			if(siz-1>=1)Tr.add(1,n*m,1,((siz-1>1)?tmp[siz-3]+1:1),tmp[siz-2],-1);
			if(siz-1>=3)Tr.add(1,n*m,1,((siz-1>3)?tmp[siz-5]+1:1),tmp[siz-4],-1);
			if(siz>=1)Tr.add(1,n*m,1,((siz>1)?tmp[siz-2]+1:1),tmp[siz-1],1);
			if(siz>=3)Tr.add(1,n*m,1,((siz>3)?tmp[siz-4]+1:1),tmp[siz-3],1);
		}
		{
			vector<int>tmp;
			if(in(i-1,j)&&a[i-1][j]<=r)tmp.push_back(a[i-1][j]);
			if(in(i,j)&&a[i][j]<=r)tmp.push_back(a[i][j]);
			if(in(i-1,j+1)&&a[i-1][j+1]<=r)tmp.push_back(a[i-1][j+1]);
			if(in(i,j+1)&&a[i][j+1]<=r)tmp.push_back(a[i][j+1]);
			sort(tmp.begin(),tmp.end());int siz=(int)tmp.size();
			if(siz-1>=1)Tr.add(1,n*m,1,((siz-1>1)?tmp[siz-3]+1:1),tmp[siz-2],-1);
			if(siz-1>=3)Tr.add(1,n*m,1,((siz-1>3)?tmp[siz-5]+1:1),tmp[siz-4],-1);
			if(siz>=1)Tr.add(1,n*m,1,((siz>1)?tmp[siz-2]+1:1),tmp[siz-1],1);
			if(siz>=3)Tr.add(1,n*m,1,((siz>3)?tmp[siz-4]+1:1),tmp[siz-3],1);
		}
		{
			vector<int>tmp;
			if(in(i,j-1)&&a[i][j-1]<=r)tmp.push_back(a[i][j-1]);
			if(in(i,j)&&a[i][j]<=r)tmp.push_back(a[i][j]);
			if(in(i+1,j-1)&&a[i+1][j-1]<=r)tmp.push_back(a[i+1][j-1]);
			if(in(i+1,j)&&a[i+1][j]<=r)tmp.push_back(a[i+1][j]);
			sort(tmp.begin(),tmp.end());int siz=(int)tmp.size();
			if(siz-1>=1)Tr.add(1,n*m,1,((siz-1>1)?tmp[siz-3]+1:1),tmp[siz-2],-1);
			if(siz-1>=3)Tr.add(1,n*m,1,((siz-1>3)?tmp[siz-5]+1:1),tmp[siz-4],-1);
			if(siz>=1)Tr.add(1,n*m,1,((siz>1)?tmp[siz-2]+1:1),tmp[siz-1],1);
			if(siz>=3)Tr.add(1,n*m,1,((siz>3)?tmp[siz-4]+1:1),tmp[siz-3],1);
		}
		{
			vector<int>tmp;
			if(in(i,j)&&a[i][j]<=r)tmp.push_back(a[i][j]);
			if(in(i,j+1)&&a[i][j+1]<=r)tmp.push_back(a[i][j+1]);
			if(in(i+1,j)&&a[i+1][j]<=r)tmp.push_back(a[i+1][j]);
			if(in(i+1,j+1)&&a[i+1][j+1]<=r)tmp.push_back(a[i+1][j+1]);
			sort(tmp.begin(),tmp.end());int siz=(int)tmp.size();
			if(siz-1>=1)Tr.add(1,n*m,1,((siz-1>1)?tmp[siz-3]+1:1),tmp[siz-2],-1);
			if(siz-1>=3)Tr.add(1,n*m,1,((siz-1>3)?tmp[siz-5]+1:1),tmp[siz-4],-1);
			if(siz>=1)Tr.add(1,n*m,1,((siz>1)?tmp[siz-2]+1:1),tmp[siz-1],1);
			if(siz>=3)Tr.add(1,n*m,1,((siz>3)?tmp[siz-4]+1:1),tmp[siz-3],1);
		}
		Node res=Tr.qry(1,n*m,1,1,r);
		if(res.mi==4)ans+=res.s;
	}
	printf("%lld\n",ans);
	return 0;
}

C0359 【1027 B组】模拟测试

A 【1027 B组】菜菜菜

\(f_{l,r,k}\) 表示在剩下 \([l,r]\) 这个区间,先手已经吃了 \(k\) 颗菜,最后是谁赢。直接 dp 就行。

点击查看代码
#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;
}
int n,k,sum[355];char s[355];
ll gethash(int l,int r,int c1){
	return 1ll*l*(n+1)*(n+1)+1ll*r*(n+1)+c1;
}
unordered_map<ll,int>f;
int dfs(int l,int r,int c1){
	int c2=sum[l-1]+sum[n]-sum[r]-c1;
	if(c1>=k)return 0;
	if(c2>=k)return 1;
	if(f.count(gethash(l,r,c1)))return f[gethash(l,r,c1)];
	if((l-1+n-r)&1ll){
		if(dfs(l+1,r,c1)==0||dfs(l,r-1,c1)==0)return f[gethash(l,r,c1)]=0;
		else return f[gethash(l,r,c1)]=1;			
	}
	else{
		if(dfs(l+1,r,c1+(s[l]=='V'))==1||dfs(l,r-1,c1+(s[r]=='V'))==1)return f[gethash(l,r,c1)]=1;
		else return f[gethash(l,r,c1)]=0;		
	}
}
void solve(){
	n=read(),k=read();scanf("%s",s+1);
	for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(s[i]=='V');
	f.clear();puts(dfs(1,n,0)?"yes":"no");
}
signed main(){
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

B 【1027 B组】缺金木

P7497 四方喝彩

在模板题的基础上增加了一个封锁操作。考虑每个区间额外维护一些信息:这个区间内被封锁/未被封锁的位置的和,这个区间内有多少个位置被封锁了,以及这个区间被整体封锁了几次。直接做就是 \(\mathcal{O}(n\log 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,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 a[200005];
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 l,r,s,t,c,add,mul,tag;
	}c[800005];
	void pushup(int p){
		if(!c[p].tag)c[p].s=(c[ls].s+c[rs].s)%mod,c[p].t=(c[ls].t+c[rs].t)%mod,c[p].c=c[ls].c+c[rs].c;
		else c[p].t=(c[p].s+c[p].t)%mod,c[p].s=0,c[p].c=c[p].r-c[p].l+1;
	}
	void pushdown(int l,int r,int p){
		int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
		if(c[p].mul!=1){
			if(!c[ls].tag)c[ls].mul=(c[ls].mul*c[p].mul)%mod;
			if(!c[rs].tag)c[rs].mul=(c[rs].mul*c[p].mul)%mod;
			if(!c[ls].tag)c[ls].s=(c[ls].s*c[p].mul)%mod;
			if(!c[rs].tag)c[rs].s=(c[rs].s*c[p].mul)%mod;
			if(!c[ls].tag)c[ls].add=(c[ls].add*c[p].mul)%mod;
			if(!c[rs].tag)c[rs].add=(c[rs].add*c[p].mul)%mod;
			c[p].mul=1;
		}
		if(c[p].add!=0){
			if(!c[ls].tag)c[ls].s=(c[ls].s+(ln-c[ls].c)*c[p].add%mod)%mod;
			if(!c[rs].tag)c[rs].s=(c[rs].s+(rn-c[rs].c)*c[p].add%mod)%mod;
			if(!c[ls].tag)c[ls].add=(c[ls].add+c[p].add)%mod;
			if(!c[rs].tag)c[rs].add=(c[rs].add+c[p].add)%mod;
			c[p].add=0;
		}
	}
	void build(int l,int r,int p){
		c[p].l=l,c[p].r=r,c[p].add=0,c[p].mul=1,c[p].tag=0;
		if(l==r){
			c[p].s=a[l];c[p].t=0,c[p].c=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,int k){
		if(c[p].tag)return;		
		if(L<=l&&r<=R){
			c[p].add=(c[p].add+k)%mod;
			c[p].s=(c[p].s+(r-l+1-c[p].c)*k%mod)%mod;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)add(lson,L,R,k);
		if(R>mid)add(rson,L,R,k);
		pushup(p);
	}
	void mul(int l,int r,int p,int L,int R,int k){
		if(c[p].tag)return;
		if(L<=l&&r<=R){
			c[p].add=(c[p].add*k)%mod;
			c[p].mul=(c[p].mul*k)%mod;
			c[p].s=(c[p].s*k)%mod;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)mul(lson,L,R,k);
		if(R>mid)mul(rson,L,R,k);
		pushup(p);
	}
	void lock(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R){
			c[p].tag++;
			if(l==r){
				if(c[p].tag==1)c[p].t=(c[p].t+c[p].s)%mod,c[p].s=0,c[p].c=1;
			}
			else pushdown(l,r,p),pushup(p);
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)lock(lson,L,R);
		if(R>mid)lock(rson,L,R);
		pushup(p);
	}
	void unlock(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R){
			c[p].tag--;
			if(c[p].tag)return;
			if(l==r){
				c[p].s=(c[p].s+c[p].t)%mod,c[p].t=0,c[p].c=0;
			}
			else pushup(p);
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)unlock(lson,L,R);
		if(R>mid)unlock(rson,L,R);
		pushup(p);
	}
	int qry(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return (c[p].s+c[p].t)%mod;
		int mid=(l+r)>>1,res=0;pushdown(l,r,p);
		if(L<=mid)res=(res+qry(lson,L,R))%mod;
		if(R>mid)res=(res+qry(rson,L,R))%mod;
		return res;
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
vector<pii>q[200005];
signed main(){
//	freopen("kaneki.in","r",stdin);
//	freopen("kaneki.out","w",stdout);
	int n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	Tr.build(1,n,1);
	for(int t=1;t<=m;t++){
		int op=read(),l=read(),r=read();
		if(op==1){
			int x=read();
			Tr.add(1,n,1,l,r,x);
		}
		else if(op==2){
			int x=read();
			Tr.mul(1,n,1,l,r,x);
		}
		else if(op==3){
			int x=read();Tr.lock(1,n,1,l,r);
			if(t+x<=m)q[t+x].push_back(mk(l,r));
		}
		else{
			printf("%lld\n",Tr.qry(1,n,1,l,r));
		}
		for(auto x:q[t])Tr.unlock(1,n,1,x.fi,x.se);
	}
	return 0;
}
/*
7 17
7 16 4 13 0 16 20 
1 3 6 12
3 1 7 12
3 3 5 1
1 3 7 3
4 3 7

7 17
7 16 4 13 0 16 20 
1 3 6 12
3 1 7 12
3 3 5 1
1 3 7 3
4 3 7
3 2 7 15
1 1 3 3
1 2 4 7
1 3 6 9
1 2 4 3
3 1 2 17
2 4 7 1
4 1 2
4 2 3
1 3 5 9
1 2 7 9
2 2 6 14
*/

C 【1027 B组】农民王

逆天题。

D 【1027 B组】计划

P6646 [CCO2020] Shopping Plans

点击查看代码
#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;
}
vector<int>v[200005];int siz[200005],l[200005],r[200005];
struct solver{
	vector<int>num,sum,ans;int t;
	struct Node{
		int val,x,y,z;
		bool operator >(const Node &b)const{
			return val>b.val;
		}
	};
	priority_queue<Node,vector<Node>,greater<Node> >q;
	void build(int id){
		sum.resize(siz[id]+5);sum[0]=v[id][0];for(int i=1;i<siz[id];i++)sum[i]=sum[i-1]+v[id][i];
		num.resize(siz[id]+5);for(int i=0;i<siz[id];i++)num[i]=v[id][i];
		t=id;q.push((Node){sum[l[id]-1],l[id]-2,l[id]-1,siz[id]});
	}
	int action(){
		Node u=q.top();q.pop();int val=u.val,x=u.x,y=u.y,z=u.z;
		if(y+1<z)q.push((Node){val-num[y]+num[y+1],x,y+1,z});
		if(x>=0&&x+1<y)q.push((Node){val-num[x]+num[x+1],x-1,x+1,y});
		if(z==siz[t]&&x+1==y&&y+1<r[t]&&y+1<z)q.push((Node){val+num[y+1],y,y+1,z});
		return val;
	}
	int qry(int k){
		while((int)ans.size()<k){
			if(!q.empty())ans.push_back(action());
			else ans.push_back(-1);
		}
		return ans[k-1];
	}
}ds[200005];
int a[200005],c[200005],o[200005],ans[200005],fir[200005],sec[200005];
int cmp(int x,int y){
	return sec[x]-fir[x]<sec[y]-fir[y];
}
struct Node{
	int val,x,y;
	bool operator >(const Node &b)const{
		return val>b.val;
	}
};
signed main(){
	int n=read(),m=read(),k=read(),sum=0,tot=0;
	for(int i=1;i<=n;i++)a[i]=read(),c[i]=read(),v[a[i]].push_back(c[i]);
	for(int i=1;i<=m;i++)sort(v[i].begin(),v[i].end()),siz[i]=(int)v[i].size();
	for(int i=1;i<=m;i++){
		l[i]=read(),r[i]=read(),r[i]=min(r[i],siz[i]);
		if(siz[i]<l[i]){
			for(int j=1;j<=k;j++)puts("-1");
			return 0;
		}
		else if(siz[i]==l[i]){for(auto t:v[i])sum+=t;}
		else if(l[i]!=0||r[i]!=0)o[++tot]=i;
	}
	if(!tot){
		puts("0");
		for(int i=2;i<=k;i++)puts("-1");
		return 0;
	}
	for(int i=1;i<=tot;i++){
		fir[o[i]]=0,sec[o[i]]=0;
		for(int j=0;j<l[o[i]];j++)fir[o[i]]+=v[o[i]][j];
		for(int j=0;j<l[o[i]]-1;j++)sec[o[i]]+=v[o[i]][j];
		sec[o[i]]+=v[o[i]][l[o[i]]];
	}
	sort(o+1,o+tot+1,cmp);
	for(int i=1;i<=tot;i++){
		if(l[o[i]]==0)ds[i].ans.push_back(0),l[o[i]]++;
		ds[i].build(o[i]);
	}
	for(int i=2;i<=k;i++)ans[i]=-1;
	ans[1]=0;for(int i=1;i<=tot;i++)ans[1]+=fir[o[i]];
	priority_queue<Node,vector<Node>,greater<Node> >q;q.push((Node){ans[1]-fir[o[1]]+sec[o[1]],1,2});
	for(int i=2;i<=k;i++){
		if(q.empty())break;
		Node u=q.top();q.pop();int val=u.val,x=u.x,y=u.y;ans[i]=u.val;
		if(ds[x].qry(y+1)!=-1)q.push((Node){val-ds[x].qry(y)+ds[x].qry(y+1),x,y+1});
		if(x+1<=tot)q.push((Node){val-ds[x+1].qry(1)+ds[x+1].qry(2),x+1,2});
		if(y==2&&x+1<=tot)q.push((Node){val-ds[x].qry(2)+ds[x].qry(1)-ds[x+1].qry(1)+ds[x+1].qry(2),x+1,2});
	}
	for(int i=1;i<=k;i++){
		if(ans[i]==-1)puts("-1");
		else printf("%lld\n",ans[i]+sum);
	}
	return 0;
}

C0362 【1030 B组】模拟测试

wohaocai。

A 【1030 B组】小A的数字

被大家一眼秒了。

\(n\) 中二进制下为 1 的位置从大到小为 \(a_1\ldots a_t\),则 \(f(n,k)=\sum\limits_{i=n+1}^{2n}[\text{popcount}(i)=k]=\sum\limits_{i=1}^t\dbinom{a_i}{k-i}\)。然后你发现 \(f(n,k)\) 是随 \(n\) 增加不降的,因为从 \(n\)\(n+1\) 的过程中减少了 \(n+1\) 且增加了 \(2n+1,2n+2\),而 \(\text{popcount}(n+1)=\text{popcount}(2n+2)\)。所以 \(f(n,k)=m\) 的是一段区间,可以直接求。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,m,k,C[70][70];
int f(int a,int b){
	int v=0,s=0;
	for(int i=60,j=b-1;i>=0;i--)if(s+C[i][j]<a)v+=(1ll<<i),s+=C[i][j],j--;
	return v+1;
}
void sol(){
	scanf("%lld%lld",&m,&k);int l=f(m,k),r=f(m+1,k)-1;
	printf("%lld %lld\n",l,r-l+1);
}
signed main(){
	for(int i=0;i<64;i++){C[i][0]=1;for(int j=1;j<=i;j++)C[i][j]=C[i-1][j-1]+C[i-1][j];} 
	scanf("%lld",&T);while(T--)sol();
	return 0;
}

B 【1030 B组】小B的平方数

傻逼 CW 机子。

对每个 \(i\) 找到最小的 \(j\) 满足 \(j^2-i>i\),如果 \(j^2-i\le n\) 那么就可以删掉 \(i\),否则不行。可以枚举 \(j\),此时保留的 \(i\) 就是一段后缀,\(\mathcal{O}(\sqrt{n})\)。发现比较小的数都一定能被删掉,只用看后 \(3\times 10^9\) 个数能否删除。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define i128 __int128
using namespace std;
const int inf=1e18,T=3e9;
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;
}
inline void write(i128 x){
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
void solve(){
	int n=read();i128 ans=0;
	int L=max(1ll,n-T),R=n,k=max(1ll,(int)sqrt(2*L+1)-5);
	while(k*k<2*L+1)k++;
	for(int i=k,l=L,r;l<=R&&(i-1)*(i-1)<=2*n+1;i++,l=r+1){
		r=min(R,(i*i-1)/2);
		if(i*i-n>l){
			i128 x=(l+min(r,i*i-n-1)),y=(min(r,i*i-n-1)-l+1);
			if(x%2==0)x/=2;
			else y/=2;
			ans=ans+x*y;
		}
	}
	write(ans);puts("");
}
signed main(){
	int T=read();
	while(T--){
		solve();
	}
	return 0;
}

C 【1030 B组】小C的游戏

首先 \(n\le 1000\) 的部分分可以直接把 \(a,b\) 压成一个点然后跑 dij。发现对于一段 \(a\gets a+b\) 的操作可以和最开始那次 \(b\gets a\) 当成一次,相当于 \(dis_{i\times a}\gets dis_{a}+i\)。此时复杂度为 \(\mathcal{O}(n\log^2 n)\)。发现如果从 \(i\)\(iab\),可以拆分成 \(i\)\(ia\)、再到 \(iab\),显然 \(a+b\le ab\)。所以只有质数的转移是有用的,复杂度 \(\mathcal{O}(n\log n\log\log n)\)

点击查看代码
#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,tot,S,T,np[1000005],p[1000005],d[2000005],vis[2000005];
void dijkstra(int s){
	for(int i=1;i<=T;i++)d[i]=inf,vis[i]=0;
	priority_queue<pii,vector<pii>,greater<pii> >q;
	d[s]=0;q.push(mk(d[s],s));
	while(!q.empty()){
		int u=q.top().se;q.pop();
		if(vis[u]||d[u]>60)continue;
		vis[u]=1;
		for(int j=1;j<=tot;j++){
			if(d[u*p[j]]>d[u]+p[j])d[u*p[j]]=d[u]+p[j],q.push(mk(d[u*p[j]],u*p[j]));
			if(u*p[j]>n)break;
		}
		if(d[u-1]>d[u]+1)d[u-1]=d[u]+1,q.push(mk(d[u-1],u-1));
		if(u<=n)d[T]=min(d[T],d[u]+(n-1)/u+(n+u-1)/u*u-n);
		else d[T]=min(d[T],d[u]+u-n);
	}
}
signed main(){
	n=read(),T=2*n+1;if(n==1)return puts("0"),0;
	for(int i=2;i<=n;i++){
		if(!np[i])p[++tot]=i;
		for(int j=1;j<=tot&&i*p[j]<=n;j++){
			np[i*p[j]]=1;
			if(i%p[j]==0)break;
		}
	}
	dijkstra(1);
	if(d[T]>=inf)puts("QAQ");
	else printf("%d\n",d[T]+1);
	return 0;
}

D 【1030 B组】小D的环球旅行

首先如果所有点的度数都是偶数,那么答案就是所有边的边权和,考虑像欧拉回路一样走。对于奇数点,我们考虑两两配对,把这些点对间重复走的边看成新加的,此时路径上除了两端点之外的点奇偶性不变。于是问题变成在奇数点间两两配对,最小化每对的距离和,搜索即可。

点击查看代码
#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,m,ans=inf,res,f[25][25],vis[25],deg[25];
void dfs(int now,int dis){
	if(dis>inf)return;
	if(vis[now]||!deg[now]){dfs(now+1,dis);return;}
	if(now>n){ans=min(ans,dis);return;}
	for(int i=now+1;i<=n;i++)if(!vis[i]&&deg[i])vis[now]=vis[i]=1,dfs(now+1,dis+f[now][i]),vis[now]=vis[i]=0;
}
signed main(){
	n=read(),m=read();for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)f[i][j]=f[j][i]=inf;
	for(int i=1,u,v,w;i<=m;i++)u=read(),v=read(),w=read(),res+=w,f[u][v]=f[v][u]=min(f[u][v],w),deg[u]^=1,deg[v]^=1;
	for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(f[i][j]>=inf)return puts("-1"),0;
	dfs(1,0);printf("%lld\n",(ans>=inf?-1:ans+res));
	return 0;
}

C0365 【1031 B组】模拟测试

A 【1031 B组】染色游戏

定义 \(f_i\) 表示

B 【1031 B组】春风化雨

C 【1031 B组】区间

D 【1031 B组】天接云涛连晓雾

思路极其复杂且细节无敌多的贪心,不打算改。

posted @ 2023-10-03 18:50  xx019  阅读(72)  评论(1)    收藏  举报