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))枚举最高点坐标
口古口古口古