1、懂的都懂
由于本题数据范围很小,所以直接四层for循环枚举预处理所有可能的四个数的和,然后对于新图中每个数的四倍,判断是否出现过即可
C++代码:
#include<iostream>
using namespace std;
const int N=55;
int a[N];
bool st[1040];//260*4
int main(){int n,k;cin>>n>>k;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)for(int k=j+1;k<=n;k++)for(int l=k+1;l<=n;l++){int t=a[i]+a[j]+a[k]+a[l];st[t]=true;//标记出现过}while(k--){int m,flag=0;cin>>m;while(m--){int x;cin>>x;if(!st[x*4])flag=1;//判断x的四倍是否出现过,如果没出现过,则与原图不相似}if(flag)puts("No");else puts("Yes");}return 0;
}
2、芬兰木棋
- 题目分析:
要求:
- 如果仅击倒 1 根木棋,则得木棋上的分数。
- 如果击倒 2 根或以上的木棋,则只得击倒根数的分数。
- 满足以上两个条件,求获得分数最大,最少需要扔多少次大木棋
所以对于每个方向,从前往后遍历每个木棋:
1、如果木棋的分数大于1,则该木棋一定要单独扔一次大木棋,否则分数一定不是最多
2、如果出现一连串的小木棋的分数等于1,则对这一连串的小木棋扔一次大木棋即可
3、如果只有一个单独的1,则也需要单独扔一次大木棋
由此可知:最大分数一定是所有分数之和
一开始以为只要斜率相同就可以一起处理了,然后遇到全是1的就分成一次投。后来写了发现答案不对劲,斜率相同的点可能贯穿两个象限。如一、三象限,但这两个象限投木棋的方向是不一样的,一个是右上,一个是左下。所以我们需要单独处理四个象限,记得不要忘了坐标轴上的点,也有四种情况↑、↓、←、→,一共是八种情况,所以在存储每个斜率的时候还要存储它们的位置(即象限)信息
处理完以上信息之后,对于每种情况,即斜率和象限都相同的的情况
我们需要从前往后枚举每个点,但是何为从前往后呢?
根据题意,是按照到原点的距离进行排序,然后枚举每个点。由于还需判断该点的分数是否等于1,故每种情况的每个点我们要存储两个信息(该点到原点的距离、该点的分数)
typedef pair<double,int> PDI;
存储结构可以用map<PDI,vector<PDI>> mp;//pair存储斜率和象限,vector存储该种情况的每个点的信息,即距离和分数
C++代码如下:
#include<iostream>
#include<map>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
typedef pair<double,int> PDI;
typedef long long LL;
int n,x,y,p,t;
int cnt,score;//cnt存储最少扔的次数,score存储最多可获得的分数
double k;//斜率
map<PDI,vector<PDI>> mp;//PII中存储斜率和象限,vector中存储到原点的距离和分数
int get(int x,int y){//得到象限位置信息//没有按照实际的象限编号,因为只要保证八种情况的编号不同即可if(x<0&&y<0)return 1;else if(x<0&&y==0)return 2;else if(x<0&&y>0)return 3;else if(x==0&&y<0)return 4;else if(x==0&&y>0)return 5;else if(x>0&&y<0)return 6;else if(x>0&&y==0)return 7;else if(x>0&&y>0)return 8;
}
int main(){cin>>n;for(int i=1;i<=n;i++){PDI u,v;//u存储斜率和象限信息,v存储距离和分数信息cin>>x>>y>>p;score+=p;//分数+pif(x!=0)k=1.0*y/x;//求斜率else k=0;//x等于0默认k为0,方便存储int t=get(x,y);//求位置u.first=k,u.second=t;//斜率和位置v.first=sqrt((LL)x*x+(LL)y*y),v.second=p;//距离和分数mp[u].push_back(v);}for(auto t:mp){vector<PDI> k=t.second;//pair默认按照第一个参数进行排序,故会按照距离进行排序sort(k.begin(),k.end());for(int i=0;i<k.size();i++){if(k[i].second>1)cnt++;else if(k[i].second==1){while(k[i].second==1&&i<k.size())i++;cnt++;//一连串的1扔一次i--;}}}cout<<score<<" "<<cnt<<endl;return 0;
}
3、打怪升级
题意分析:
- 首先要找到空降位置
1、对每个点做一遍最短路,以怪物的能量作为路径长度,故每个点所能到达的最远的地方即为最难攻克的堡垒
2、对于每个点所能到的最远的地方对应的路径,找出这些路径的最小值,该最小值所对应的起点就是空降位置
- 其次就是处理每个询问
1、用空降位置作为起点再跑一遍最短路
2、依次输出每次询问的最短路径和最大价值
C++代码如下:
#include<iostream>
#include<cstring>
using namespace std;
const int N=1010,INF=0x3f3f3f3f;
int g[N][N],w[N][N];
int dist[N],val[N];//dist存储最短路径,val[i]存的是走到该点最大的价值
bool st[N];
int pre[N];//pre[i]存走到该点的路径
int n,m,k,start,maxv=INF;;
//start存储空降位置,maxv存储每个点攻克最难攻克的堡垒所需的能量的最小值
void dijkstra(int u){//dijkstra算法求最短路模板memset(dist,0x3f,sizeof dist);memset(st,false,sizeof st);dist[u]=0;for(int i=0;i<n;i++){int t=-1;for(int j=1;j<=n;j++)if(!st[j]&&(t==-1 ||dist[j]<dist[t]))t=j;st[t]=true;for(int j=1;j<=n;j++){if(dist[j]>dist[t]+g[t][j]){dist[j]=dist[t]+g[t][j];//更新距离 val[j]=val[t]+w[t][j];//更新价值 pre[j]=t;//j的上一个点是t }else if(dist[j]==dist[t]+g[t][j]){//如果路径能量相同,则选择缴获武器价值最高的方案if(val[j]<val[t]+w[t][j]){val[j]=val[t]+w[t][j];//更新价值 pre[j]=t;//j的上一个点是t }}}}
}
int main(){memset(g,0x3f,sizeof g);scanf("%d%d",&n,&m);for(int i=1;i<=m;i++){int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);g[a][b]=g[b][a]=c;//记录花费w[a][b]=d,w[b][a]=d;//记录价值}for(int i=1;i<=n;i++){//每一个点跑一下最短路,找到最合算的空降位置startdijkstra(i);int temp=0;//计算攻下最难攻克的堡垒所需要的能量for(int i=1;i<=n;i++)temp=max(temp,dist[i]);if(temp<maxv)maxv=temp,start=i;//如果能量小于maxv,则更新maxv和start}printf("%d\n",start);memset(val,0,sizeof val);memset(pre,0,sizeof pre);dijkstra(start);//从出发点出发,走到每个节点//处理k个询问scanf("%d",&k);while(k--){int x;scanf("%d",&x);int u=x,path[N],cnt=0;//path存储路径while(u){path[cnt++]=u;u=pre[u];//u更新为路径中它的前一个节点}for(int i=cnt-1;i>=0;i--){if(i>0)printf("%d->",path[i]);else printf("%d",path[i]);}printf("\n%d %d\n",dist[x],val[x]);}return 0;
}
4、疫情防控
题意分析:每天给出一个防控地区,若干个当天的行程,判断有多少个行程不能成形
1、暴力做法,可以骗17分
对于每个防控地区,都标记一下,然后dfs搜索从起点到终点是否有不经过防控地区的路线,如果有,则可以出行,如果没有,则不能出行
#include<iostream>
#include<cstring>
using namespace std;
const int N=50010,M=400010;
int h[N],e[M],ne[M],idx;
int state[N];//标记机场是否已经成为防控地区
bool st[N];
int n,m,d,flag;
void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int sta,int ed){//开始机场和结束机场st[sta]=true;if(state[sta])return;for(int i=h[sta];i!=-1;i=ne[i]){int j=e[i];if(j==ed&&state[j]==false){flag=1;return;}if(!st[j]&&!state[j])dfs(j,ed);}st[sta]=false;
}
int main(){cin>>n>>m>>d;memset(h,-1,sizeof h);while(m--){int a,b;cin>>a>>b;add(a,b),add(b,a);}while(d--){int c,q,ans=0;cin>>c>>q;state[c]=true;//c为防控地区while(q--){memset(st,false,sizeof st);int x,y;flag=0;cin>>x>>y;dfs(x,y);if(!flag)ans++;}cout<<ans<<endl;}return 0;
}
2、正解:并查集
分析:对于每个询问,我们要找是否有一条不经过任何防控地区的路线。换句话说,就是将所有不是防控地区的机场看成若干个连通块,然后判断起点和终点是否连通,如果连通,则可以出行,否则不能
假设一开始所有的机场就是若干个连通块,每次询问的时候就把当天变成防控地区的机场踢出连通块,然后就会发现这样并不好操作
正难则反易,虽然删除连通块中的点很麻烦,但是反过来想,往连通块里加点是很简单的,这可以通过并查集来完成
所以,我们可以离线处理,即先存储所有的询问,然后逆序处理每一天的询问,每次将一个机场加入到连通块中
C++代码:
#include<iostream>
#include<vector>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
vector<int> v[N];//存储所有边
vector<PII> query[N];//query[i]存储第i天的所有询问,每个询问的起点和终点
int p[N];//并查集数组
int day[N],ans[N];//day[i]:第i天变成防控地区的机场编号,ans[i]:第i天不能出行的路线数
int n,m,d;
int find(int x){//找祖宗节点 + 路径压缩 if(p[x]!=x)p[x]=find(p[x]);return p[x];
}
int main(){cin>>n>>m>>d;for(int i=1;i<=n;i++)p[i]=i;//初始化并查集for(int i=1;i<=m;i++){int a,b;cin>>a>>b;//建一条无向边v[a].push_back(b);v[b].push_back(a);}//存储询问 for(int i=1;i<=d;i++){int k; cin>>day[i]>>k;//第i天的防控机场存入到day[i]中 p[day[i]]=0;//0标记该机场为防控机场while(k--){int a,b;cin>>a>>b;query[i].push_back({a,b});//第i天的所有询问都存入query[i]中} } //把所有没有在任何一天变成防控地区的机场合并成若干个连通块 for(int i=1;i<=n;i++)if(p[i]){for(int j=0;j<v[i].size();j++)//遍历所有的相邻节点if(p[v[i][j]])p[find(v[i][j])]=find(i);}//逆序处理每一天 for(int i=d;i>=1;i--){//先处理当天的每个询问 for(int j=0;j<query[i].size();j++){int fa=find(query[i][j].first);int fb=find(query[i][j].second);if(fa!=fb||fa==0)ans[i]++;//如果两个节点不连通或者都是防控地区,则该天不能出行的询问加一 }//然后把该天变为防控地区的机场变成正常机场 int k=day[i];p[k]=k;for(int j=0;j<v[k].size();j++)if(p[v[k][j]])p[find(v[k][j])]=k;//合并所有相邻的正常机场 }for(int i=1;i<=d;i++)cout<<ans[i]<<endl;//输出每一天的答案 return 0;
}