5月CWOI杂题

C0238 【0503 B组】模拟测试

垫底。

A 【1026 B组】bins

有一个结论:将两部分各自排序后对应装入是最优的,因为假如有交叉的选择,你交换后一定不劣。

发现 \(m\le1000\),可以把数列丢到值域上用后缀和判断。因为需要严格大于,可以挪一位。

code:

我写了线段树,其实不用。

点击查看代码
#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(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int m,n,a[20005];
int cntA[20005],cntB[20005],A[20005],B[20005];
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 mi,tag;
	}c[80005];
	void pushup(int p){
		c[p].mi=min(c[ls].mi,c[rs].mi);
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		c[ls].mi+=c[p].tag,c[rs].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].mi=B[l]-A[l];return;}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int L,int R,int k){
		if(L>R)return;
		if(L<=l&&r<=R){
			c[p].mi+=k,c[p].tag+=k;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)update(lson,L,R,k);
		if(R>mid)update(rson,L,R,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L>R)return 0;
		if(L<=l&&r<=R){
			return c[p].mi;
		}
		int mid=(l+r)>>1,res=inf;pushdown(l,r,p);
		if(L<=mid)res=min(res,query(lson,L,R));
		if(R>mid)res=min(res,query(rson,L,R));
		return res;
	}
}Tr;
signed main(){
	m=read(),n=read();
	for(int i=1;i<=n;i++)a[i]=read(); 
	for(int i=1,j=n/2+1;i<=n/2;i++,j++)cntA[a[i]]++,cntB[a[j]]++;
	for(int i=m;i>=1;i--)A[i]=A[i+1]+cntA[i];
	for(int i=m-1;i>=1;i--)B[i]=B[i+1]+cntB[i+1];
	Tr.build(1,m,1);
	for(int k=n/2;k>=1;k--){
		if(Tr.query(1,m,1,1,m)>=0)return printf("%lld\n",k),0;
		Tr.update(1,m,1,1,a[k],1);
		Tr.update(1,m,1,1,a[k]-1,1);
		Tr.update(1,m,1,1,a[2*k-1]-1,-1);
		Tr.update(1,m,1,1,a[2*k]-1,-1);
	}
	puts("0");
	return 0;
}

B 【1026 B组】inversions

\(c_{i,0/1}\) 表示把该序列分成 \(2^{n-i}\) 个连续的长度均为 \(2^i\) 的段,每段左边对右边产生的逆/顺序对个数。手玩可以发现,对于一个 \(q_i=x\) 的操作,相当于对于所有 \(j\le i\),交换 \(c_{j,0}\)\(c_{j,1}\)。初始的 c 数组可以用归并求。

code:

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define ull unsigned 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;
}
ull k1,k2;
ull xorShift128Plus(){
	ull k3=k1,k4=k2;
	k1=k4;
	k3^=(k3<<23);
	k2=k3^k4^(k3>>17)^(k4>>26);
	return k2+k4;
}
int a[1100005],b[1100005],q[1000005],p[1100005];
void gen(int n,int m,int threshold,ull _k1,ull _k2){
	k1=_k1,k2=_k2;
	for(int i=1;i<=(1<<n);i++)a[i]=xorShift128Plus()%threshold+1,p[i]=a[i];
	for(int i=1;i<=m;i++)q[i]=xorShift128Plus()%(n+1);
}
int n,m,all,c[2][25],threshold;ull _k1,_k2;
void merge(int l,int r,int o,int t){
	if(l==r)return;
	int mid=(l+r)>>1;
	merge(l,mid,o-1,t);merge(mid+1,r,o-1,t);
	int i=l,j=mid+1,len=l;
	while(i<=mid&&j<=r){
		if(a[i]<=a[j])b[len++]=a[i++];
		else b[len++]=a[j++],c[t][o]+=mid-i+1;
	}
	while(i<=mid)b[len++]=a[i++];
	while(j<=r)b[len++]=a[j++];
	for(int k=l;k<=r;k++)a[k]=b[k];
}
signed main(){
	n=read(),m=read(),threshold=read(),_k1=read(),_k2=read();
	gen(n,m,threshold,_k1,_k2);merge(1,1ll<<n,n,0);
	for(int i=1;i<=(1ll<<n);i++)a[i]=-p[i];merge(1,1ll<<n,n,1);
	for(int j=1,ans;j<=m;j++){
		for(int i=0;i<=q[j];i++)swap(c[0][i],c[1][i]);
		ans=0;for(int i=0;i<=n;i++)ans+=c[0][i];
		all^=(ans*j);
	}
	printf("%lld\n",all);
	return 0;
}

C 【1026 B组】candies

有一种较为暴力的做法:枚举我们要改的位置,对剩下的做一个背包,那么加入的物品一定会使得背包为 1 的位置变为两倍(不看 0)。要最大化答案,可以先找出最大的方案,再枚举加入物品的体积,过程可以用 bitset 优化。

(注意:下文中提到“为 0/1 的位置”均指的是在最优的方案中,不考虑会改变的元素,剩余的做背包的那个数组。)

提一嘴:虽然这个做法不太能过,但加上一个强力剪枝能过:发现对于为 1 的位置的集合 \({i_1,i_2\ldots i_s}\) 和为 0 的位置的集合 \({j_1,j_2\ldots j_t}\)(满足 \(j_1>i_1\),也就是 \(i_1\) 以前的为 0 的位置不管),\(i_k\) 在左移后必须安排到一个为 0 的位置,也就是至少为 \(j_k\)。我们枚举移动步数时可以利用这个结论跳一下,不用一个一个试。这样虽然挺玄学(我没写),但好像几乎卡不掉。

正解是你发现左移的步数 x 必须满足对于任意为 1 的位置 \(i<j\) 都要满足 \(j-i\neq x\)。有一个神奇的写法:

点击查看代码
B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);

最后 B 中不为 1 的就是可行的答案。为什么这是对的?因为 \(j-i\) 这个式子可以看作有 \(n\) 个体积为 \(b_i\)\(n\) 个体积为 \(-b_i\) 的物品做背包。

虽然能过,但还是有点慢,怎么优化?你可以先对所有物品跑背包,每次在这个基础上撤销掉这个物品的影响,得到的就是其余的背包。但是我们需要记录方案数,存不下怎么办?可以对大数取模,相信概率。

code:

点击查看代码
//每次暴力背包
#include<bits/stdc++.h>
#define int 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;
}
bitset<1386005>B;int b[105],cnt[105];
signed main(){
	int n=read(),id=1,s=0;
	for(int i=1;i<=n;++i)b[i]=read(),s+=b[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;++i){
		if(b[i]==b[i-1])continue;
		B.reset();B[0]=1;
		for(int j=1;j<=n;++j)if(i!=j)B|=(B<<b[j]);
		cnt[i]=(int)B.count();
		if(cnt[i]>cnt[id])id=i;
	}
	B.reset();B[0]=1;
	for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
	for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
	for(int i=0;i<=1386000;++i)if(B[i]==0)return printf("%lld %lld\n",b[id],i),0;
	return 0;
}
点击查看代码
//优化
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int b[105],s[105],cnt[105],f[700005],g[700005];bitset<700005>B;
signed main(){
	int n,id=1,all=1;scanf("%d",&n);f[0]=1;for(int i=1;i<=n;++i)scanf("%d",&b[i]);
	sort(b+1,b+n+1);for(int i=1;i<=n;++i)s[i]=s[i-1]+b[i];
	for(int i=1;i<=n;++i)for(int j=s[i];j>=b[i];--j){
		if(f[j]!=0)all--;
		f[j]=(f[j]+f[j-b[i]])%mod;
		if(f[j]!=0)all++;
	}
	for(int i=1;i<=n;++i){
		if(b[i]==b[i-1])continue;
		cnt[i]=all;
		memcpy(g,f,sizeof(f));
		for(int j=b[i];j<=s[n];++j){
			if(g[j]!=0)cnt[i]--;
			g[j]=(g[j]-g[j-b[i]]+mod)%mod;
			if(g[j]!=0)cnt[i]++;
		}
		if(cnt[i]>cnt[id])id=i;
	}
	B.reset();B[0]=1;
	for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
	for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
	for(int i=1;i<=s[n]-b[id]+1;++i)if(B[i]==0)return printf("%d %d\n",b[id],i),0;
	return 0;
}

D 【1026 B组】sheep

还不会。

C0236 【0509 C组】模拟测试

欢 乐 赛。

A 【0505 C组】寻找羔羊

送分。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
char s[30005];
signed main(){
	scanf("%s",s+1);int n=strlen(s+1),ans=0;
	vector<int>v;
	for(int i=1;i+4<=n;i++){
		if(s[i]=='a'&&s[i+1]=='g'&&s[i+2]=='n'&&s[i+3]=='u'&&s[i+4]=='s')v.push_back(i);
	}
	for(int i=0;i<(int)v.size();i++){
		int l=v[i],r=v[i]+4;
		if(i==0)ans+=(l-1+1)*(n-r+1);
		else ans+=(l-v[i-1])*(n-r+1);
	}
	printf("%lld\n",ans);
	return 0;
}

B 【0505 C组】Find and Replace

你可以根据题中的操作建一个 dag。更具体地,定义 \(p_{i,c}\) 表示“初始为字符 \(c\),经历 \([i,n]\) 的操作”。比如倒序枚举操作,每次 \(p_{i,c_i}\rightarrow p_{i+1,s_j}\)。最后求 \([l,r]\) 内的字符就是求第 \([l,r]\) 个叶子,dfs 即可。

两个细节:可能子树内叶子数会存不下,但是发现 \(l,r\le10^{18}\);一个字符变成另一个字符这种情况会构成链,跳起来很慢。但是你发现这两个点可以看成一个。

code:

点击查看代码
#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;
}
string c[200005],s[200005];
vector<int>g[6000005];
int T,root[200005][30],siz[6000005];
void dfs(int u,int l,int r){
	if(g[u].empty()){
		printf("%c",(char)(u-1+'a'));
		return;
	}
	int L=l,R=r;
	for(auto v:g[u]){
		if(L>siz[v]){L-=siz[v],R-=siz[v];continue;}
		dfs(v,max(1ll,L),min(siz[v],R));L-=siz[v],R-=siz[v];
		if(R<=0)break;
	}
}
signed main(){
	int l=read(),r=read(),q=read();
	for(int i=1;i<=q;i++)cin>>c[i]>>s[i];
	c[0]="a",s[0]="a";
	for(int o=0;o<26;o++)root[q+1][o]=++T,siz[root[q+1][o]]=1;
	for(int i=q;i>=0;i--){
		int o=c[i][0]-'a';
		if((int)s[i].size()==1){
			root[i][o]=root[i+1][s[i][0]-'a'];
		}
		else{
			root[i][o]=++T;
			for(int j=0;j<(int)s[i].size();j++){
				g[root[i][o]].push_back(root[i+1][s[i][j]-'a']);
				siz[root[i][o]]+=siz[root[i+1][s[i][j]-'a']];
				if(siz[root[i][o]]>=inf)siz[root[i][o]]=inf;
			}			
		}
		for(int j=0;j<26;j++){
			if(j!=o)root[i][j]=root[i+1][j];
		}
	}
	dfs(root[0][0],l,r);
	return 0;
}

C 【0505 C组】关灯

唉,傻逼了。

因为异或运算的性质,可以把那个第一步的影响放到最后加进去。假设总共进行 \(m\) 次操作,第 \(i\) 次操作的第一步选了位置 \(x\),则这次操作会使得 \(x,x+1\ldots x+m-i\) 这一长度为 \(m-i+1\) 的子串翻转(当然,是循环的)。把它们倒过来看,就是第 \(i\) 次翻转长度为 \(i\) 的一段。定义 \(f_{i,j}\) 表示只考虑第一步的贡献,能否在第 \(i\) 步得到状态 \(j\)。这个因为 \(n\) 相等可以 \(O(n^22^n)\) 预处理。求答案就是枚举总的操作次数,则二、三步的影响是固定的,可以算出来。

可以证明答案上界至多为 \(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 T,n,f[25][1050005];
int shift(int pos,int len){
	if(pos+len-1>=n)return (1ll<<n)-(1ll<<pos)+((1ll<<(pos+len-n))-1);
	else return (1ll<<(pos+len))-(1ll<<pos);
}
int circle(int x){
	int o=(x>>(n-1))&1ll;x-=(o<<(n-1));
	return (x<<1)+o;
}
signed main(){
	T=read(),n=read();f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1ll<<n);j++){
			for(int k=0;k<n;k++){
				f[i][j^shift(k,i)]|=f[i-1][j];
			}
		}
	}
	while(T--){
		int a=0,b=0,s=0;
		for(int i=0,o;i<n;i++)scanf("%1lld",&o),a|=(o<<i);
		for(int i=0,o;i<n;i++)scanf("%1lld",&o),b|=(o<<i);
		for(int i=0;i<=n;i++){
			if(f[i][a^s]){printf("%lld\n",i);break;}
			s^=b,b=circle(b);
		}
	}
	return 0;
}

D 【0505 C组】哞路线

思考 \(n=2\) 的特殊情况。结论:

\[ans=\begin{cases}\dbinom{\frac{a_2}{2}-1}{\frac{a_1}{2}-1}&a_1\le a_2\\\ \dbinom{\frac{a_1}{2}}{\frac{a_2}{2}}&a_1>a_2\end{cases} \]

解释:

第一种情况,0 到 2 \(\frac{a_1}{2}\) 次,1 到 2 \(\frac{a_2-a_1}{2}\) 次。因为 1 到 2 一定是在 0 到 2 的基础上选一个“寄生”得到,等价于 \(\frac{a_2-a_1}{2}\) 个东西放到 \(\frac{a_1}{2}\) 个盒子里,可以空,化简得到上式。

第二种情况,0 到 2 \(\frac{a_2}{2}\) 次,0 到 1 \(\frac{a_1-a_2}{2}\) 次。两种走法独立,相当于在 \(\frac{a_2}{2}+\frac{a_1-a_2}{2}\) 个东西中选 \(\frac{a_2}{2}\) 个作为第一类,化简得到上式。

扩展到一般的形式。对于 \((a_i,a_{i+1})\),这个子任务是独立的,把相邻的答案乘起来就是答案。

code:

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

C0244 【0524 C组】模拟测试

蠢蠢。

A 【0902 BC组】数列

把生成树建出来,按顺序枚举每条边。红边可以直接染色,蓝边 \((u,v)\) 等价于 \(u\rightarrow v\) 路径上的红边需要在它之前染色。可以 dsu,也可以线段树。

点击查看代码
#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,w,nxt;
}e[1000005];
int head[500005],tot;
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int pa[500005][21],dep[500005],val[500005];
void dfslca(int u,int fa){
	dep[u]=dep[fa]+1,pa[u][0]=fa;
	for(int i=0;i<19;i++)pa[u][i+1]=pa[pa[u][i]][i];
	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;dfslca(v,u);
	}
}
int getlca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	for(int i=19;i>=0&&dep[x]!=dep[y];i--)if(dep[y]-dep[x]>=(1<<i))y=pa[y][i];
	if(x==y)return x;
	for(int i=19;i>=0;i--)if(pa[x][i]!=pa[y][i])x=pa[x][i],y=pa[y][i];
	return pa[x][0];
}
int n,m,siz[500005],son[500005];
void dfs1(int u,int fa){
	siz[u]=1,son[u]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs1(v,u);siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])son[u]=v;
	}
}
int cur,dfn[500005],rnk[500005],top[500005];
void dfs2(int u,int rt){
	top[u]=rt,dfn[u]=++cur,rnk[cur]=u;
	if(son[u])dfs2(son[u],rt);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==pa[u][0])continue;
		if(v!=son[u])dfs2(v,v);
	}
}
vector<int>vec;
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,rn;
	}c[2000005];
	void pushup(int p){
		c[p].s=(c[ls].s+c[rs].s);
		c[p].rn=max(c[ls].rn,c[rs].rn);
	}
	void build(int l,int r,int p){
		if(l==r){
			c[p].s=0;
			c[p].rn=l;
			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=k;
			if(k==0)c[p].rn=l;
			else c[p].rn=-inf;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(lson,x,k);
		else update(rson,x,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].s;
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res=res+query(lson,L,R);
		if(R>mid)res=res+query(rson,L,R);
		return res;
	}
	int ask(int l,int r,int p,int L,int R){
		if(L<=l&&r<=R)return c[p].rn;
		int mid=(l+r)>>1,res=-inf;
		if(L<=mid)res=max(res,ask(lson,L,R));
		if(R>mid)res=max(res,ask(rson,L,R));
		return res;
	}
	void addPath(int u,int lca){
		if(u==lca)return;
		while(dep[top[u]]>dep[lca]){
//			printf("{%lld,%lld}\n",u,top[u]);
			while(1){
				int pos=ask(1,n,1,dfn[top[u]],dfn[u]);
				if(pos==-inf)break;
//				printf("|%lld,%lld|---|%lld,%lld|\n",u,top[u],pos,rnk[pos]);
				update(1,n,1,pos,1),vec.push_back(val[rnk[pos]]);
			}
			u=pa[top[u]][0];
		}
		if(u==lca)return;
		while(1){
			int pos=ask(1,n,1,dfn[lca],dfn[u]);
			if(pos==-inf)break;
//			printf("|%lld,%lld|---|%lld,%lld|\n",u,top[u],pos,rnk[pos]);
			if(rnk[pos]!=lca)update(1,n,1,pos,1),vec.push_back(val[rnk[pos]]);
			else break;
		}
	}
	#undef ls
	#undef rs
	#undef lson
	#undef rson
}Tr;
struct Path{
	int u,v,c;
}g[500005];
int ans[500005];
signed main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	n=read(),m=read();
	for(int i=1,u,v,c;i<=m;i++){
		u=read(),v=read(),c=read();
		g[i]=(Path){u,v,c};
		if(c==1)add(u,v,i),add(v,u,i);
	}
	dfslca(1,0);dfs1(1,0);dfs2(1,1);
	Tr.build(1,n,1);int col=0;
	for(int i=1,u,v,c;i<=m;i++){
		if(ans[i])continue;
		u=g[i].u,v=g[i].v,c=g[i].c;
		if(c==1){
			ans[i]=++col;
			if(pa[v][0]==u)Tr.update(1,n,1,dfn[v],1);
			else Tr.update(1,n,1,dfn[u],1);
		}
		else{
			int lca=getlca(u,v);vec.clear();
			Tr.addPath(u,lca);
//			printf("1 %lld YES\n",i);
			Tr.addPath(v,lca);
//			printf("2 %lld YES\n",i);
			sort(vec.begin(),vec.end());
			for(auto x:vec)ans[x]=++col;
			ans[i]=++col;
		}
	}
	for(int i=1;i<=m;i++)printf("%d ",ans[i]);
	return 0;
}

B 【0902 BC组】方差

原题,我好蠢。

结论:我们一定会选一些点(其实就是分割区间的分割点),把会覆盖它们的区间全部删掉。

先假设所有区间都删掉。定义 \(f_i\) 表示 \(i\) 是最后一个分割点,能保留的最大和。转移的时候倒着枚举,维护前 k 大可以做到 \(n^2\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;
}
struct Line{
	int l,r,w;
}s[2505];
int b[5005],f[5005];
vector<int>vec[5005];
signed main(){
	freopen("interval.in","r",stdin);
	freopen("interval.out","w",stdout);
	int n=read(),k=read(),tot=0,all=0;
	for(int i=1;i<=n;i++){
		s[i].l=b[++tot]=read();
		s[i].r=b[++tot]=read();
		s[i].w=read();all+=s[i].w;
	}
	sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;i++){
		s[i].l=lower_bound(b+1,b+tot+1,s[i].l)-b;
		s[i].r=lower_bound(b+1,b+tot+1,s[i].r)-b;
		vec[s[i].l].push_back(i);
	}
	f[0]=0;int ma=-inf;
	for(int i=1;i<=tot;i++){
		f[i]=0;
		priority_queue<int,vector<int>,greater<int> >q;
		int sum=0;
		for(int j=i;j>=1;j--){
			for(auto x:vec[j]){
				if(s[x].r<=i){
					sum+=s[x].w,q.push(s[x].w);
					while((int)q.size()>k)sum-=q.top(),q.pop();
				}
			}
			f[i]=max(f[i],f[j-1]+sum);
		}
		ma=max(ma,f[i]);
	}
	printf("%lld\n",all-ma);
	return 0;
}

C 【1014 B组】简单题

定义 \(f_{l,r}\) 表示区间 \([l,r]\) 的答案,枚举区间最大值的位置 \(i\),有转移式 \(f_{l,r}=\max\limits_{i=l}^j\left\{f_{l,i-1}+f_{i+1,r}+\max\limits_{j=1}^{k_i}\left\{v_{i,j}\cdot g(l,r,i)-c_{i,j}\right\}\right\}\),其中 \(g(l,r,i)=\sum\limits_{x=l}^i\sum\limits_{y=i}^rq_{x,y}\)

这里其实应该强制规定 \(v_{i,j}=\max\limits_{i=l}^r\left\{a_i\right\}\),但是我们发现假如区间最大值位置为 \(p\),则我们枚举从 \(p\) 转移的时候答案一定不劣,所以规定与否并不影响最终答案。

优化:求 \(\max\limits_{j=1}^{k_i}\left\{v_{i,j}\cdot g(l,r,i)-c_{i,j}\right\}\) 可以维护 \(n\) 个凸包,每次二分查找最大值。

点击查看代码
#include<bits/stdc++.h>
#define int __int128
#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;
}
inline void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10+'0');
}
int q[305][305],s[305][305],f[305][305];
int ask(int x1,int y1,int x2,int y2){
	return s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
}
struct Point{
	int x,y;
	bool operator <(const Point &b)const{
		if(x!=b.x)return x<b.x;
		return y>b.y;
	}
};
struct Convex{
	vector<Point>v;
	vector<int>q;
	int k,L,R,id;
	int P(int i,int j){
		return (-v[j].y)-(-v[i].y);
	}
	int Q(int i,int j){
		return (-v[j].x)-(-v[i].x);
	}
	void init(int n,int i){
		k=n;id=i;L=1,R=0;q.resize(k+5);
		v.push_back((Point){-inf,-inf});
	}
	void build(){
		for(int i=1;i<=k;i++){
			if(v[i].x==v[i-1].x)continue;
			while(L<R&&P(q[R],i)*Q(q[R-1],q[R])>=P(q[R-1],q[R])*Q(q[R],i))R--;
			q[++R]=i;
		}
	}
	int find(int val){
		int l=L+1,r=R,res=L;
		while(l<=r){
			int mid=(l+r)>>1;
			if(P(q[mid-1],q[mid])<Q(q[mid-1],q[mid])*val)res=mid,l=mid+1;
			else r=mid-1;
		}
		return res;
	}
	int query(int l,int r){
		int pos=find(ask(l,id,id,r));
		return v[q[pos]].y-ask(l,id,id,r)*v[q[pos]].x;
	}
}p[305];
signed main(){
	int n=read();
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			q[i][j]=read();
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+q[i][j];
		}
	}
	for(int i=1,k;i<=n;i++){
		k=read();p[i].init(k,i);
		for(int j=1,v,c;j<=k;j++){
			v=read(),c=read();
			p[i].v.push_back((Point){-v,-c});
		}
		sort(p[i].v.begin(),p[i].v.end());
		p[i].build();
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i<=j)f[i][j]=-inf;
			else f[i][j]=0;
		}
	}
	for(int len=1;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int i=l;i<=r;i++){
				f[l][r]=max(f[l][r],f[l][i-1]+f[i+1][r]+p[i].query(l,r));
			}
		}
	}
	print(f[1][n]);
	return 0;
}

D 【1014 B组】数数题

先看 \(u=1\) 的部分分怎么做。定义 \(s_i\) 表示 \(a\) 的前缀和,\(d_i\) 表示 \(i\) 到根节点的期望距离,有 \(d_i=c_i+\sum\limits_{j=1}^{i-1}\dfrac{a_j}{s_{i-1}}(d_j+c_j)\),可以前缀和。

对于 \(u\neq1\) 的情况,答案就是 \(d_u+d_v-2\sum\limits_{l=1}^uP_ld_l\),其中 \(P_i\) 表示 \(i\)\((u,v),u<v\) 的 lca 的概率。下面分 \(l\neq u\)\(l=u\) 两种情况讨论:

  • \(l\neq u\)

    对于每个点 \(o\),根据 \(o\) 的大小分类讨论:

    • \(u<o<v\)

      此时点 \(o\) 只能在 \(l\rightarrow v\) 的路径上。

      \[\begin{aligned} P&=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{u+1,u+2\ldots v-1\}}\dfrac{a_l}{s_{p_1-1}}\dfrac{a_{p_m}}{s_{v-1}}\prod\limits_{i=2}^m\dfrac{a_{p_{i-1}}}{s_{{p_i}-1}}\\ &=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{u+1,u+2\ldots v-1\}}\dfrac{a_l}{s_{v-1}}\prod\limits_{i=1}^m\dfrac{a_{p_i}}{s_{{p_i}-1}}\\ &=\dfrac{a_l}{s_{v-1}}\prod\limits_{i=u+1}^{v-1}(1+\dfrac{a_i}{s_{i-1}})\\ &=\dfrac{a_l}{s_{v-1}}\prod\limits_{i=u+1}^{v-1}\dfrac{s_i}{s_{i-1}}\\ &=\dfrac{a_l}{s_{v-1}}\dfrac{s_{v-1}}{s_x}\\ &=\dfrac{a_l}{s_x}\\ \end{aligned} \]

    • \(l<o<u\)

      此时点 \(o\) 即可以在 \(l\rightarrow u\) 的路径上,也可以在 \(l\rightarrow v\) 的路径上。

      \[\begin{aligned} P&=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{l+1,l+2\ldots u-1\}}\dfrac{a_l}{s_{p_1-1}}\dfrac{2a_{p_m}}{s_{u-1}}\prod\limits_{i=2}^m\dfrac{2a_{p_{i-1}}}{s_{{p_i}-1}}\\ &=\sum\limits_{\{p_1,p_2\ldots p_m\}\subseteq\{l+1,l+2\ldots u-1\}}\dfrac{a_l}{s_{u-1}}\prod\limits_{i=1}^m\dfrac{2a_{p_i}}{s_{{p_i}-1}}\\ &=\dfrac{a_l}{s_{u-1}}\prod\limits_{i=l+1}^{u-1}(1+\dfrac{2a_i}{s_{i-1}})\\ \end{aligned} \]

      这里为什么概率是二倍?根据式子,你把 \(l<o<u\) 放到两边的路径上都会在原来的概率上乘 \(\dfrac{a_o}{s_{o-1}}\),两种方案各算一遍就是乘 2 了。

      两坨东西乘起来就是概率,都可以预处理。

  • \(l=u\)

    延续刚刚 \(u<o<v\) 的式子,\(P=\dfrac{a_x}{s_x}\)

直接做是 \(O(n\log P),P=10^9+7\) 的,但是优化一下求逆元可以 \(O(\log P+n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
	return res;
}
int a[1000005],s[1000005],c[1000005],d[1000005],e[1000005],f[1000005],k[1000005],p[1000005],v[1000005],jc[1000005],iv[1000005],ij[1000005];
int solve(int x,int y){
	if(x==y)return 0;
	if(x>y)swap(x,y);
	int res=(d[x]+d[y])%mod;
	res=(res-2*(s[x-1]*v[x]%mod+k[x]*d[x]%mod)%mod+mod)%mod;
	return res;
}
signed main(){
	int n=read(),q=read();
	for(int i=1;i<n;i++)a[i]=read();
	for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
	for(int i=1;i<=n;i++)c[i]=read();
	jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*s[i]%mod;
	ij[n]=qpow(jc[n],mod-2);for(int i=n-1;i>=1;i--)ij[i]=ij[i+1]*s[i+1]%mod;
	for(int i=1;i<=n;i++)iv[i]=ij[i]*jc[i-1]%mod;
	for(int i=1;i<=n;i++)v[i]=iv[i-1]*iv[i]%mod;
	for(int i=1;i<=n;i++)k[i]=a[i]*iv[i]%mod;
	d[1]=0,e[1]=(d[1]+a[1]*c[1]%mod)%mod;
	for(int i=2;i<=n;i++)d[i]=(e[i-1]*iv[i-1]%mod+c[i])%mod,e[i]=(e[i-1]+(d[i]+c[i])%mod*a[i]%mod)%mod;
	f[1]=1;for(int i=2;i<=n;i++)f[i]=(2*a[i]%mod*iv[i-1]+1)%mod;
	jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*f[i]%mod;
	ij[n]=qpow(jc[n],mod-2);for(int i=n-1;i>=1;i--)ij[i]=ij[i+1]*f[i+1]%mod;
	for(int i=1;i<=n;i++)p[i]=a[i]*a[i]%mod*ij[i]%mod*d[i]%mod;
	for(int i=1;i<=n;i++)v[i]=v[i]*jc[i-1]%mod;
	for(int i=1;i<=n;i++)s[i]=(s[i-1]+p[i])%mod;
	for(int i=1,u,v;i<=q;i++)u=read(),v=read(),printf("%lld\n",solve(u,v));
	return 0;
}
posted @ 2023-05-02 19:49  xx019  阅读(20)  评论(0)    收藏  举报