The 2021 Sichuan Provincial Collegiate Programming Contest
E.Don’t Really Like How The Story Ends
E.Don’t Really Like How The Story Ends
题意:给你一个无向图,然你进行dfs,同时你可以进行加边操作,使得最后dfs序是1~n。
解题思路:dfs,我们在进行dfs时遍历该节点的子节点(事先对该节点的所有子节点进行排序),如果有目标节点则先将目标加一再对原来的目标进行dfs,如果没有目标点而且大于目标点,我们就需要加边,其实加边都加在根节点1上,此时还要将目标加一再对原来的目标进行dfs。那什么时候退出递归呢,我们在一开始可以加入一个虚拟节点n+1,当我们遍历到这个虚拟节点就可以退出了。
总而言之:
1.碰到小于目标的continue
2.碰到等于目标的,更新目标继续递归
3.碰到大于目标的,加边继续递归
#include <bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define ll long long //#define int long long const int maxn = 1e5 + 20; const int inf = 0x3f3f3f3f; const ll INF = 1ll << 62; const double eps = 1e-7; const int mod = 1e6 + 3; #define mem(a, b) memset(a, b, sizeof(a)) inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); } inline int rd() { int x; scanf("%d", &x); return x; } int n, m, res, want; vector<int>e[maxn]; void addEdge(int u, int v){ e[u].push_back(v); e[v].push_back(u); } void dfs(int u){ if(u == n + 1)return; for(auto v : e[u]){ if(v < want)continue; while(v >= want){//更新目标,加边,利用循环可以不断添加(可以手动模拟理解) if(v == want) want++, dfs(want - 1); else want++, res++, dfs(want - 1); } } } int main(){ int T = rd(); while(T--){ n = rd(); m = rd(); for(int i = 1; i <= n; i++)e[i].clear(); for(int i = 0; i < m; i++){ int u = rd(); int v = rd(); addEdge(u, v); } addEdge(1, n + 1);//增加虚拟节点 for(int i = 1; i <= n; i++)sort(e[i].begin(), e[i].end()); res = 0; want = 2; dfs(1); printf("%d\n", res); } return 0; }
题意:给出一个n个点m条边的无向图,给出a1,a2,.....an 对于每条无向边你可以让它成为有向边,记每个点的度为di 求(d1-a1+d2-a2+.....+dn-an)的最小值
思路:
代码:会建图了套下板子就行
#include<bits/stdc++.h> using namespace std; const int MAXN=6000,MAXM=101000; const int INF=0x3f3f3f3f; struct edge{ int to,next,cap,flow,cost; }e[MAXM]; int head[MAXN],cnt; int pre[MAXN],dis[MAXN]; bool vis[MAXN]; int N; 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; } void init(int n){ N=n; cnt=0; memset(head,-1,sizeof head); } void add(int u,int v,int cap,int cost){ e[cnt].to=v; e[cnt].cap=cap; e[cnt].cost=cost; e[cnt].flow=0; e[cnt].next=head[u]; head[u]=cnt++; } bool spfa(int s,int t){ queue<int>q; for(int i=0;i<MAXN;i++){ dis[i]=INF; vis[i]=false; pre[i]=-1; } dis[s]=0; vis[s]=true; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=false; for(int i=head[u];~i;i=e[i].next){ int v=e[i].to; if(e[i].cap>e[i].flow&&dis[v]>dis[u]+e[i].cost){ dis[v]=dis[u]+e[i].cost; pre[v]=i; if(!vis[v]){ vis[v]=true; q.push(v); } } } } int ed=t; if(pre[t]==-1)return false; else return true; } int minCostMaxflow(int s,int t,int &cost){ int flow=0; cost=0; while(spfa(s,t)){ int Min=INF; for(int i=pre[t];~i;i=pre[e[i^1].to]){ if(Min>e[i].cap-e[i].flow) Min=e[i].cap-e[i].flow; } for(int i=pre[t];~i;i=pre[e[i^1].to]){ e[i].flow+=Min; e[i^1].flow-=Min; cost+=e[i].cost*Min; } flow+=Min; } return flow; } int main(){ int T=read(); while(T--){ int n=read(),m=read(),s=800,t=801; init(n); vector<int>a(n+1); for(int i=1+n;i<=m+n;i++){ add(s,i,1,0); add(i,s,0,0); } for(int i=1;i<=n;i++){ cin>>a[i]; int u=i,v=t; add(u,v,a[i],0); add(v,u,0,0); add(u,v,INF,1); add(v,u,0,-1); } for(int i=1+n;i<=n+m;i++){ int v1,v2,u=i; cin>>v1>>v2; // if(v1==v2)continue; add(u,v1,1,0); add(v1,u,0,0); add(u,v2,1,0); add(v2,u,0,0); } int cost; int maxflow=minCostMaxflow(s,t,cost); vector<int>ans; for(int u=n+1;u<=n+m;u++){ int i=head[u]; ans.push_back(!e[i].flow); } printf("%d\n",cost); for(auto x:ans)cout<<x; cout<<endl; } }
L. Spicy Restaurant
题意:很多人想要去低于等于自己能够承受辣度的店面,店面由自己的辣度,店面之间存在道路,人在不同的店面,问每个人的最短距离是多少?因为店面和人的数量达到1e5,询问由5e5(⊙﹏⊙),所以不能暴力。
解题思路:多源bfs,对每一种辣度进行bfs求出某一个点到某一个辣度的最小距离,然后就可以进行O(n)的询问了
多源bfs
void bfs(int pos){ queue<int>q; for(int i=1;i<=n;i++)vis[i]=0; for(int i=1;i<=n;i++){ if(ld[i]==pos)q.push(i),dis[i][pos]=0,vis[i]=1;//记录每一个当前辣度的店铺点 } //多源bfs就是把多个点放进queue中,然后就和普通的bfs没区别了 while(!q.empty()){ int index=q.front(); q.pop(); for(int i=0;i<ve[index].size();i++){ int net=ve[index][i]; if(!vis[net]){ vis[net]=1; dis[net][pos]=dis[index][pos]+1; q.push(net); } } } }
完整代码
#include "bits/stdc++.h" using namespace std; const int maxn=1e5+10; const int inf=0x3f3f3f3f; int n,m,q; int ld[maxn],vis[maxn]; vector<int>ve[maxn]; int dis[maxn][110]; inline int rd(){ int a; scanf("%d",&a); return a; } void bfs(int pos){ queue<int>q; for(int i=1;i<=n;i++)vis[i]=0; for(int i=1;i<=n;i++){ if(ld[i]==pos)q.push(i),dis[i][pos]=0,vis[i]=1; } while(!q.empty()){ int index=q.front(); q.pop(); for(int i=0;i<ve[index].size();i++){ int net=ve[index][i]; if(!vis[net]){ vis[net]=1; dis[net][pos]=dis[index][pos]+1; q.push(net); } } } } int main(){ memset(dis,inf,sizeof dis); n=rd();m=rd(); q=rd(); for(int i=1;i<=n;i++){ ld[i]=rd(); } for(int i=1;i<=m;i++){ int a=rd(),b=rd(); ve[a].push_back(b); ve[b].push_back(a); } for(int i=1;i<=100;i++){ bfs(i); } // for(int i=1;i<=n;i++){ // for(int j=1;j<=5;j++){ // cout<<"shop"<<i<<" to ld"<<j<<" =="<<dis[i][j]<<endl; // } // } for(int i=1;i<=q;i++){ int a=rd(),b=rd(); int ans=inf; for(int j=1;j<=b;j++)ans=min(ans,dis[a][j]); if(ans==inf)printf("-1\n"); else printf("%d\n",ans); } return 0; }
Don’t Really Like How The Story Ends
Don’t Really Like How The Story Ends
题意:给了一群1到n的行星,部分行星之间存在路径,问你添加最少几条路径使得从1号点dfs结果可以是1到n
题解:存在三种情况
1.i和i+1号行星存在路径,那就直接dfs第i+1号行星
2.i和i+1号行星之间没有路径,那就添加一条路径,然后dfs第i+1号行星,然后回溯的时候再遍历和i号点相连的行星
3.回溯到1号行星都没有路径和第i号行星相连,就添加一条路径,然后dfs第i号行星
关键节点:
1.情况1和2
if(net==now){ vis[now]=1; dfs(net,now); }else{ cnt++; vis[now]=1; dfs(now,now); i--; }
2.情况3
while(now<=n){ // cout<<now<<" "<<cnt<<endl; vis[now]=1;if(now!=1)cnt++;dfs(now,now); }
AC代码:
#include "bits/stdc++.h" using namespace std; const int maxn=1e5+10; const int inf=0x3f3f3f3f; inline int rd(){ int a; scanf("%d",&a); return a; } int t,n,m; vector<int>ve[maxn]; int now,cnt,vis[maxn]; void dfs(int pos,int &now){ now++; for(int i=0;i<ve[pos].size();i++){ // cout<<ve[pos][i]<<" "<<now<<endl; int net=ve[pos][i]; if(vis[net])continue; if(net==now){ vis[now]=1; dfs(net,now); }else{ cnt++; vis[now]=1; dfs(now,now); i--; } } } int main(){ t=rd(); while(t--){ n=rd();m=rd(); for(int i=1;i<=n;i++){ ve[i].clear(); } for(int i=1;i<=m;i++){ int a=rd(),b=rd(); if(a>b)ve[b].push_back(a); else ve[a].push_back(b); } for(int i=1;i<=n;i++){ sort(ve[i].begin(),ve[i].end()); vis[i]=0; } now=1;cnt=0; while(now<=n){ // cout<<now<<" "<<cnt<<endl; vis[now]=1;if(now!=1)cnt++;dfs(now,now); } printf("%d\n",cnt); } return 0; }
Monster Hunter
怪猎狂喜题
题意:有n只怪物,给你m个攻击,伤害就是循环执行的那种(伤害值就是 1 to 3 ),然后问你最少要多少次攻击才能击杀所有怪物,嘿嘿
题解:本来以为是贪心,emm。。。。确实没错,是二分加贪心
二分能够击杀所有怪物的次数,然后就得到了每一种伤害能够使用的次数,然后用贪心去杀怪物,能杀完就减少次数继续二分,不能也二分,直到出结果、
也就是说用二分把题目变成一个填充值为1、2、3的贪心问题,然后重点来了
贪心策略:1.对于每一个血量大于等于3的奇数的怪物用3去打一次伤害,把这一类的奇数血量打成偶数,(方便后面用2和1去填充)
2.对于血量大于等于6的怪物用一组一组的3去打伤害,打到最后就只剩下1、2、4三种类型的血量或者3打完了(就直接1、2贪心不要接下来的步骤了)
3.如果我们还有3多,那就直接从多的血量打到少的血量,再从少的血量打到多的血量,(就是n到1再到n的意思)来回碾压一遍,因为最大是4所以必定能血量打完,然后因为有1、2的存在,所以必定3打的完,否则要么没1、2,要么就能下一个二分了
4.3打完了以后就用1、2伤害贪心,先用2用到没,再用1补充
AC代码
#include "bits/stdc++.h" using namespace std; #define int long long #define ll long long const int maxn=1e5+100; inline int rd(){ int a; scanf("%lld",&a); return a; } int t,n,m; int a[maxn],num[maxn][10],hp[maxn]; int check(int h[],vector<int>ve){ int hp[m+10]; for(int i=0;i<m;i++)hp[i]=h[i]; //情况1 for(int i=0;i<m;i++){ if(hp[i]>=3&& (hp[i]&1) &&ve[2]){ hp[i]-=3; ve[2]--; } } //情况2 for(int i=0;i<m;i++){ if(hp[i]>=6&&ve[2]) { int num = min(hp[i] / 6, ve[2] / 2); hp[i] -= num *6; ve[2] -= num*2; } } //情况3 sort(hp,hp+m); for(int i=m-1;i>=0;i--){ if(ve[2]&&hp[i]){ hp[i]=max(0ll,hp[i]-3); ve[2]--; } } for(int i=0;i<m;i++){ if(ve[2]&&hp[i]){ hp[i]=max(0ll,hp[i]-3); ve[2]--; } } //情况4 for(int i=0;i<m;i++){ if(ve[1]&&hp[i]){ int num=min(hp[i]/2,ve[1]); hp[i]-=2*num; ve[1]-=num; } } for(int i=0;i<m;i++){ if(ve[1]&&hp[i]){ hp[i]=max(0ll,hp[i]-2); ve[1]--; } } for(int i=0;i<m;i++){ if(ve[0]&&hp[i]){ int num=min(hp[i],ve[0]); ve[0]-=num; hp[i]-=num; } } //看最大的怪物血量是不是0 sort(hp,hp+m); if(hp[m-1]!=0)return 0; return 1; } signed main(){ t=rd(); while(t--){ n=rd(); for(int i=1;i<=n;i++){ a[i]=rd(); for(int j=0;j<3;j++)num[i][j]=num[i-1][j]; num[i][a[i]-1]++;//前缀和记录第i个攻击时有几个1,几个2,几个3 } m=rd(); for(int i=0;i<m;i++){ hp[i]=rd(); } int l=0,r=1e16;//二分伤害,wa4就是r太小了 while(l<r){ int mid=(l+r)>>1; vector<int>ve; int t=mid/n;int w=mid%n;//t就是有几个组,w就是多出来的 for(int i=0;i<3;i++){ ve.push_back(num[w][i]+t*num[n][i]); //所以有几个i的伤害就是t组*每组里i的个数加上前w里有几个i } if(check(hp,ve)==1){ r=mid; }else l=mid+1; } printf("%lld\n",l); } return 0; }

浙公网安备 33010602011771号