CSP 取消了,只能 vp 一下。

T1

先枚举每个点,bfs 出中转次数不超过 \(k\) 次的点,并标记 \(f[i][j]=f[j][i]=1\)

发现 \(1\)\(A,B,C,D\) 构成的环可以拆成 \(1,A,B\)\(1,D,C\),这里想了好一会枚举 \(A,D\) 怎么做,结果发现自己弱智了。已经确定 \(1\),在 \(n=2500\) 的背景下选择了枚举 \(B\)\(C\) ,然后预处理 \(A,D\)

考虑到可能会重复,例如通过 \(B\) 选出的 \(A\) 可能与 \(C,D\) 相同,所以我们最多只需要记录前三大即可。

预处理出对于 \(i\) 满足满足 \(f[i][j]=1,f[j][1]=1\) 的分数前三大的点 \(j\),记为 \(\max_1[i],\max_2[i],\max_3[i]\)

我们钦定 \(B<C\),枚举 \(B,C\),判断然后取最大值即可。

复杂度 \(O(n^2)\),常数 \(\frac{9}{2}\)

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}

void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10^48);
}

const int N=2505,M=1e4+5,K=105;
int n,m,k,a[N],head[N],cnt;
struct node{
	int next,to;
}e[M<<1];

void add(int from,int to){
	e[++cnt]=(node){head[from],to};
	head[from]=cnt;
}

bool vis[N],f[N][N];
queue<int>q;
int dis[N];

void bfs(int x){
	while(!q.empty())q.pop();
	for(int i=1;i<=n;++i)vis[i]=0,dis[i]=1e18;
	q.push(x),dis[x]=-1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		if(dis[u]>=k)break;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			f[x][v]=f[v][x]=1;
			dis[v]=min(dis[v],dis[u]+1);
			if(!vis[v])q.push(v);
		}
	}
}

int maxn[4][N];

signed main(){
	n=read(),m=read(),k=read();
	for(int i=2;i<=n;++i)a[i]=read();
	for(int i=1;i<=m;++i){
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	for(int i=1;i<=n;++i){
		bfs(i);
	}
	for(int i=2;i<=n;++i){
		for(int j=2;j<=n;++j){
			if(i==j)continue;
			if(f[i][j]&&f[j][1]){
				if(a[j]>a[maxn[1][i]]){
					maxn[3][i]=maxn[2][i];
					maxn[2][i]=maxn[1][i];
					maxn[1][i]=j;
				}else if(a[j]>a[maxn[2][i]]){
					maxn[3][i]=maxn[2][i];
					maxn[2][i]=j;
				}else if(a[j]>a[maxn[3][i]]){
					maxn[3][i]=j;
				}
			}
		}
	}
	int ans=0;
	for(int i=2;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			if(!f[i][j])continue;
			for(int k1=1;k1<=3;++k1){
				if(maxn[k1][i]==j)continue;
				for(int k2=1;k2<=3;++k2){
					if(maxn[k1][i]!=maxn[k2][j]&&maxn[k2][j]!=i&&f[maxn[k1][i]][1]&&f[maxn[k2][j]][1]){
						ans=max(ans,a[i]+a[j]+a[maxn[k1][i]]+a[maxn[k2][j]]);
					}
				}
			}
		}
	}
	print(ans);
	return 0;
}

T2

考虑最优策略是什么,可以从暴力中发掘:

枚举小 L 选的每一个值,小 Q 选择乘积最小的值,而小 L 从这些值里面选取最大的。

根据暴力理解完题目之后,不能把目光只停留在列举每一个数中,要考虑数的形成过程,即两区间内选数相乘。

也就是说:对于小 L 的每种情况,小 Q 都有相应的对策,这个对策在形式上是固定的,而小 L 根据每一种情况反馈出的值选择对于他来说最优的。

先考虑 \(A_i,B_i>0\) 的情况,对于小 L 选的每一个正数 \(A_i\),小 Q 形式上固定的对策是选择区间内最小的 \(B_i\) 与之相乘保证最小,对此,小 L 将会选区间内最大的 \(A_i\) 使乘积最大。

再考虑正负零的情况,稍微列举归纳可发现,我们将数分为非负数和负数:

对于小 L 选这两种情况,小 Q 也有固定的策略:

  • 小 L 选非负数,小 Q 有负数选负数,没有负数选最小的非负数,总结一下可以得到小 Q 的策略是:选最小的。
  • 小 L 选负数,小 Q 有非负数选最大的非负数,没有非负数选最大的负数,总结一下可以得到小 Q 的策略是:选最大的。

所以我们只需要预处理 \(6\) 组值:

  • \(A\) 序列非负数 \(\max_1,\min_1\)
  • \(A\) 序列负数 \(\max_2,\min_2\)
  • \(B\) 序列 \(\max_3,\min_3\)

用 ST 表实现即可,复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
using namespace std;

#define int long long 

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}

void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10^48);
}

const int N=1e5+5;
int n,m,q,a[N],b[N],num[N],lgn,lgm;
int maxn1[N][21],minn1[N][21];
int maxn2[N][21],minn2[N][21];
int maxn3[N][21],minn3[N][21];

void init_maxn1(){
	for(int i=1;i<=n;++i)maxn1[i][0]=a[i];
	for(int j=1;j<=lgn;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			maxn1[i][j]=max(maxn1[i][j-1],maxn1[i+(1ll<<(j-1))][j-1]);
		}
	}
}

void init_minn1(){
	for(int i=1;i<=n;++i){
		if(a[i]>=0)minn1[i][0]=a[i];
		else minn1[i][0]=1e18;
	}
	for(int j=1;j<=lgn;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			minn1[i][j]=min(minn1[i][j-1],minn1[i+(1ll<<(j-1))][j-1]);
		}
	}
}

void init_maxn2(){
	for(int i=1;i<=n;++i){
		if(a[i]<0)maxn2[i][0]=a[i];
		else maxn2[i][0]=-1e18;
	}
	for(int j=1;j<=lgn;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			maxn2[i][j]=max(maxn2[i][j-1],maxn2[i+(1ll<<(j-1))][j-1]);
		}
	}
}

void init_minn2(){
	for(int i=1;i<=n;++i)minn2[i][0]=a[i];
	for(int j=1;j<=lgn;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			minn2[i][j]=min(minn2[i][j-1],minn2[i+(1ll<<(j-1))][j-1]);
		}
	}
}

void init_maxn3(){
	for(int i=1;i<=m;++i)maxn3[i][0]=b[i];
	for(int j=1;j<=lgm;++j){
		for(int i=1;i+(1<<j)-1<=m;++i){
			maxn3[i][j]=max(maxn3[i][j-1],maxn3[i+(1ll<<(j-1))][j-1]);
		}
	}
}

void init_minn3(){
	for(int i=1;i<=m;++i)minn3[i][0]=b[i];
	for(int j=1;j<=lgm;++j){
		for(int i=1;i+(1<<j)-1<=m;++i){
			minn3[i][j]=min(minn3[i][j-1],minn3[i+(1ll<<(j-1))][j-1]);
		}
	}
}

signed main(){
	n=read(),m=read(),q=read();
	lgn=(int)log2(n),lgm=(int)log2(m);
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=m;++i)b[i]=read();
	for(int i=1;i<=n;++i){
		if(a[i]>=0)num[i]=num[i-1]+1;
		else num[i]=num[i-1];
	}
	init_maxn1(),init_minn1();
	init_maxn2(),init_minn2();
	init_maxn3(),init_minn3();
	int l1,r1,l2,r2,ans,lga,lgb,tmpa,tmpb;
	while(q--){
		l1=read(),r1=read(),l2=read(),r2=read();
		ans=-1e18;
		lga=(int)log2(r1-l1+1);
		lgb=(int)log2(r2-l2+1);
		if(num[r1]-num[l1-1]>0){
			tmpb=min(minn3[l2][lgb],minn3[r2-(1<<lgb)+1][lgb]);
			if(tmpb>=0){
				tmpa=max(maxn1[l1][lga],maxn1[r1-(1<<lga)+1][lga]);
				ans=max(ans,tmpa*tmpb);
			}else{
				tmpa=min(minn1[l1][lga],minn1[r1-(1<<lga)+1][lga]);
				ans=max(ans,tmpa*tmpb);
			}
		}
		if(num[r1]-num[l1-1]<r1-l1+1){
			tmpb=max(maxn3[l2][lgb],maxn3[r2-(1<<lgb)+1][lgb]);
			if(tmpb>=0){
				tmpa=max(maxn2[l1][lga],maxn2[r1-(1<<lga)+1][lga]);
				ans=max(ans,tmpa*tmpb);
			}else{
				tmpa=min(minn2[l1][lga],minn2[r1-(1<<lga)+1][lga]);
				ans=max(ans,tmpa*tmpb);
			}
		}
		print(ans),puts("");
	}
	return 0;
}

T3

题目比较难读,用了快半个小时才读懂题……

人话翻译:

\(n\) 个点,\(m\)​ 条边,要求支持:

  • 删边 \((u,v)\)
  • 删去所有以 \(u\) 为终点的点
  • 加边 \((u,v)\)
  • 加上所有以 \(u\) 为终点的点

在每一次操作之后判断:

  • 是否每个点的出度为 \(1\)
  • 是否每个点都能走到一个环

很显然第一个判断是包括了第二个的,所以我们只用判断:是否每个点的出度为 \(1\)

赛时:\(O(mq)\) 模拟做法显然好做,然后没有 \(t=2\)\(t=4\) 的情况只需要我们支持每次加删一条边和判断,用一个 multiset 维护出度判断首尾是否相同即可。对于有 \(t=2\) 的,我们用一个集合维护未被摧毁的边,由于每次加边只能加一条,复杂度是有保证的。这是 \(60\),然后还写了一个用集合维护删边的,复杂度没有保证想骗点分,结果好像没骗到。码了整整两百行。

正解是 hash,看到判断每个点的出度全为 \(1\),我们考虑给每个点随机赋一个权值 \(val_i\),设出来一个与出度有关的 \(h(u)\),用 \(\sum h(u)\) 来判断。

然后考虑如何应对上述操作。发现以 \(u\) 为终点很逆天,于是我们建反图把它干掉。

将图转化为反图,需要支持的操作变为:

  • 删边 \((u,v)\)
  • 删去所有以 \(u\) 为起点的点
  • 加边 \((u,v)\)
  • 加上所有以 \(u\) 为起点的点

此时我们需要判断:

  • 每个点的入度为 \(1\)

对于删去所有和恢复所有边,需要我们记录所有点的初始出度和当前出度。

但是判断的是入度,应该怎么办呢?

考虑把当前点的贡献转移到起点,即对于边 \(u\to v\),我们不记录 \(v\) 的入度,而记录 \(u\) 的出度,把 \(val_v\) 记录到 \(h(u)\) 上,这样既不会改变最终答案,又能解决出入度冲突问题。

所以我们设 \(h(u)\) 为以 \(u\) 为起点的边的终点权值和,这满足对于每个点入度都为 \(1\) 的图他们的 \(\sum h(u)\) 相同,即一个点的权值只会被统计一次。

为了保证正确率,可以多随机几个点的权值。

#include<bits/stdc++.h>
using namespace std;

#define int long long

inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c<='9'&&c>='0'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}

void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x>9)print(x/10);
	putchar(x%10^48);
}

const int N=5e5+5;
const int MAX=1e9+7;
int n,m,q;
int d[N],nd[N],val[N][5],ans[5];
int nsum[N][5],nSum[5],sum[N][5];

signed main(){
	n=read(),m=read();
	srand(time(0));
	for(int i=1;i<=n;++i){
		for(int j=0;j<5;++j){
			val[i][j]=rand()*rand()%MAX;
			ans[j]+=val[i][j];
		}
	}
	for(int i=1;i<=m;++i){
		int u=read(),v=read();
		d[v]++,nd[v]++;
		for(int j=0;j<5;++j){
			sum[v][j]+=val[u][j];
			nsum[v][j]+=val[u][j];
			nSum[j]+=val[u][j];
		}
	}
	q=read();
	int t,u,v;
	while(q--){
		t=read();
		if(t==1){
			u=read(),v=read();
			nd[u]--;
			for(int i=0;i<5;++i)nsum[v][i]-=val[u][i],nSum[i]-=val[u][i];
		}else if(t==2){
			u=read();
			nd[u]=0;
			for(int i=0;i<5;++i)nSum[i]-=nsum[u][i],nsum[u][i]=0;
		}else if(t==3){
			u=read(),v=read();
			nd[u]++;
			for(int i=0;i<5;++i)nsum[v][i]+=val[u][i],nSum[i]+=val[u][i];
		}else{
			u=read();
			nd[u]=d[u];
			for(int i=0;i<5;++i)nSum[i]+=(sum[u][i]-nsum[u][i]),nsum[u][i]=sum[u][i];
		}
		bool flag=1;
		for(int i=0;i<5;++i)if(ans[i]!=nSum[i])flag=0;
		if(flag)puts("YES");
		else puts("NO");
	} 
	return 0;
}

T4

开 T4 的时候时间已经不多了,然后就写挂了 \(8\) 分。

看完这题我直接就写了个把 \(s\)\(t\) 之间的链扯下来 dp,然后就挂在了样例 \(2\)

可以发现当 \(k=3\) 时是能跳出链外的。

于是我在写的 dfs 上加了对于 \(x\) 向外枚举 \(3\) 层转移,然后加上 \(k=1\) 就交了……

赛后发现挂分了,写了个拍可以发现存在这样一种情况:\(x\)\(s\)\(t\) 最优的中转点,而如果只从 \(s\) 开始 dfs 可能先到 \(t\) 再到 \(x\)

对于这种情况,我们只需要跑 \(n\) 遍 dfs 就写了(

这样就有 \(44\) 分。

正解是倍增维护矩阵,鉴定为是我考场上写不出来的。

先扔这了,有时间再补。

原文地址:http://www.cnblogs.com/Daidly/p/16846087.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性