暑期集训day23考试整理

T1: 猫和狗

题目大意:\(k\)个观众,每个观众喜欢一条猫或狗,讨厌一条猫或狗(前猫后狗或前狗后猫),问最多满足多少个观众

1.暴力(\(25pts\)

考场分数:\(5pts\)
原因:

暴力枚举每个状态,一条猫或狗只有选和不选两种状态,\(O(2^n)\)枚举就完事

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=500+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,K,hate[maxn],like[maxn],black[maxn],ans,dclock,vis[maxn];
signed main(){

	n=read(),m=read(),K=read();
	for(int i=1;i<=K;i++){
		char ch1,ch3;
		int ch2,ch4;
		cin>>ch1>>ch2>>ch3>>ch4;
		if(ch1=='C'){
			if(!vis[ch2])vis[ch2]=++dclock;
			if(!vis[ch4+n])vis[ch4+n]=++dclock;
			like[i]=vis[ch2],hate[i]=vis[ch4+n];
		}
		if(ch1=='D'){
			if(!vis[ch2+n])vis[ch2+n]=++dclock;
			if(!vis[ch4])vis[ch4]=++dclock;
			like[i]=vis[ch2+n],hate[i]=vis[ch4];
		}
	}
	long long maxs=(1<<dclock)-1;
	for(long long s=1;s<=maxs;s++){
		memset(black,0,sizeof(black));
		int sum=0;
		for(int i=1;i<=dclock;i++){
			if(s&(1LL*1<<i-1)){
				for(int j=1;j<=K;j++){
					if(like[j]==i)black[j]++;
					if(hate[j]==i)black[j]--;
				}
			}
		}
		for(int i=1;i<=K;i++){
			if(black[i]==1)sum++;
		}
		ans=max(ans,sum);
	}
	cout<<ans;
	return 0;
}

2.二分图(\(100pts\)

万万想不到,\(T1\)竟然是二分图原题(然而最后放在二分图的练习的最后一道,压根就没做)

由于一个观众,喜欢猫就讨厌狗,喜欢狗就讨厌猫

所以可以逆向思维,用二分图把冲突的情况处理掉

把喜欢这个动物的与讨厌这个动物的相连,跑二分图,最后答案为:\(n-tot/2\)(有\(tot\)个冲突,说明只能满足一半)

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=500+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;咕咕
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,K,ans,a[maxn][maxn],girls[maxn],vis[maxn];
char s1[maxn][maxn],s2[maxn][maxn];
vector<int> g[maxn];
bool Find(int u){
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!vis[v]){
			vis[v]=1;
			if(!girls[v]||Find(girls[v])){girls[v]=u;return 1;}
		}
	}
	return 0;
}
int main(){
	n=read(),m=read(),K=read();
	for(int i=1;i<=K;i++){
		cin>>s1[i]>>s2[i];
	}
	for(int i=1;i<=K;i++){
		for(int j=1;j<=K;j++){
			if(!strcmp(s1[i],s2[j]))g[i].push_back(j),g[j].push_back(i);
		t}
	}
	for(int i=1;i<=K;i++){
		memset(vis,0,sizeof(vis));
		if(Find(i))ans++;
	}
	cout<<K-ans/2;
	return 0;
}

T2:旋转子段

题目大意:翻转某个子段,使得翻转后整个序列中满足\(a[i]==i\)的个数最多

1.\(O(n^3)\) 枚举(\(35pts\)

三维,第一维枚举区间长度,第二维枚举左右端点,第三维扫区间,柿子:\(ans=max(sum[l-1]+sum[n]-sum[r]+num)\)\(num\)为区间内满足条件个数)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=5000+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int sum[maxn],n,f[maxn],a[maxn],ans;
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		sum[i]=sum[i-1];
		if(a[i]==i)sum[i]++;	
	}
	ans=sum[n];
	if(sum[n]==n)return cout<<n,0;
	int now=0;
	for(int d=2;d<=n;d++){
		for(int i=1,j;(j=d+i-1)<=n;i++){
			now=sum[i-1]+sum[n]-sum[j];
			for(int k=i;k<=j;k++){
				if(a[k]==j+i-k)now++;
			}
			ans=max(ans,now);
		}
	}
	cout<<ans;
	return 0;
}

2.\(O(n^2)\)枚举(\(65pts\)

这里已经用了一个特殊性质了:\(l=min(a[i],i)\) \(r=max(a[i],i)\)

因此三维枚举可以直接转化为:一维枚举中点,第二维左右扩展

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=5e5+50;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,a[maxn],sum[maxn],ans,l,r,cnt;
int main(){
	n=read();
	for(register int i=1;i<=n;i++){
		a[i]=read();
		sum[i]=sum[i-1];
		if(a[i]==i)sum[i]++;
	}
	for(register int i=1;i<=n;i++){
		x=a[i];
		l=min(x,i),r=max(i,x);
		if(l==r)continue;
		cnt=0;
		while(l<=r){
			if(l!=r){
				if(a[l]==r)cnt++;
				if(a[r]==l)cnt++;
			}else if(l==r){
				if(a[l]==l)cnt++;
			}
			l++,r--;
		}
		ans=max(ans,cnt+sum[min(x,i)]+sum[n]-sum[max(x,i)]);
	}
	cout<<ans<<endl;
}

3.桶+\(STL\)\(100pts\)

再细细观察,又能得到第二个性质:如果存在\(a[i]+i==a[j]+j\),那么以\(\frac{i+j}{2}\)为中点,且翻转区间包含\(i\)\(j\),翻转后\(i\)\(j\)都能满足条件

证明略了,干瞪眼观察得到

因此,可以开个桶维护一下\(a[i]+i\),然后枚举每一个\(a[i]+i\),由之前的性质可得到左端点和右端点,然后像前两个一样搞它就完事了

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+5,INF=0x3f3f3f3f;
int n,sum[maxn],a[maxn],ans;
vector<int> ve[maxn];
bool cmp(int x,int y){return abs(2*x-a[x]-x)<abs(2*y-a[y]-y);}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum[i]=sum[i-1];
		if(a[i]==i)sum[i]++;
		ve[a[i]+i].push_back(i);
	}
	for(int i=2;i<=2*n;i++){
		if(!ve[i].empty()){
			sort(ve[i].begin(),ve[i].end(),cmp);
			for(int j=0;j<ve[i].size();j++){
				int l=ve[i][j],r=a[l];
				if(l>r)swap(l,r);
				ans=max(ans,sum[l-1]+sum[n]-sum[r]+j+1);
			}
		}
	}
	cout<<ans;
	return 0;
}

3.走格子

题目大意:一个矩阵,一堆能走的点,期间能在墙上开传送门,然后求从起点到终点的最短路线

感觉是考试中较简单的一道题了,然而打的正解挂了\(75pts\),原因是没注意必须要在墙上开门,以为可以原地开门(其实就是没考虑全,毕竟很多时候要开传送门的格子旁边就是墙)

乱搞就完事了,一个格子对上下左右能走的地方连边

然后考虑传送门,由于传送门只能由墙边传到墙边,所以可以直接从当前格子往上下左右走,看最后停的位置,然后连边,边权为上下左右中最短的距离\(+1\),因为必须要走到墙边才能打传送门,如图

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1000+5,maxe=6e6+5,INF=0x3f3f3f3f;
inline int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n,m,ans=INF,tx,vis[maxe],ty,qx,qy,head[maxe],tot,dis[maxe],inq[maxe];
char a[maxn][maxn];
struct Edge{
	int to,next,from,dis;
}e[maxe];
void Add(int x,int y,int z){
	e[++tot].next=head[x];
	e[tot].to=y;
	e[tot].from=x;
	e[tot].dis=z;
	head[x]=tot;
}
void Spfa(int S){
	for(int i=0;i<=n*m*2;i++)dis[i]=INF;
	dis[S]=0;
	queue<int> q;q.push(S);
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;//cout<<u<<endl;
		for(int x=head[u];x;x=e[x].next){
			int v=e[x].to;
			if(dis[v]>dis[u]+e[x].dis){
				dis[v]=dis[u]+e[x].dis;
				if(!vis[v]){
				//	if(++inq[v]>n*m)break;
					q.push(v);vis[v]=1;
				}
			}
		}
	}
	if(dis[(tx-1)*m+ty]==INF)puts("no");
	else cout<<dis[(tx-1)*m+ty]<<endl;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			if(a[i][j]=='C')qx=i,qy=j,a[i][j]='.';
			if(a[i][j]=='F')tx=i,ty=j,a[i][j]='.';
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]=='.'){
				if(a[i+1][j]=='.')Add((i-1)*m+j,i*m+j,1);
				if(a[i-1][j]=='.')Add((i-1)*m+j,(i-2)*m+j,1);
				if(a[i][j+1]=='.')Add((i-1)*m+j,(i-1)*m+j+1,1);
				if(a[i][j-1]=='.')Add((i-1)*m+j,(i-1)*m+j-1,1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]=='.'){
				int x1=i,x2=i,y1=j,y2=j,diss=INF;
				while(a[x1+1][j]=='.')x1++;
				while(a[i][y1+1]=='.')y1++;
				while(a[x2-1][j]=='.')x2--;
				while(a[i][y2-1]=='.')y2--;
				diss=min(min(x1-i+1,i-x2+1),min(y1-j+1,j-y2+1));
				Add((i-1)*m+j,(x1-1)*m+j,diss);
				Add((i-1)*m+j,(x2-1)*m+j,diss);
				Add((i-1)*m+j,(i-1)*m+y1,diss);
				Add((i-1)*m+j,(i-1)*m+y2,diss);
			}
		}
	}
	Spfa((qx-1)*m+qy);
	return 0;
}

T4:柱状图

模拟退火,\(O(1)\)随机搞最大高度,\(O(n)\)枚举最高点坐标

口古口古口古

posted @ 2020-08-05 19:26  _乀aakennes  阅读(136)  评论(0编辑  收藏  举报
levels of contents