比较生疏的知识点,当时看着一眼就觉得是图论,甚至想到了最大流,但是因为建不来图,被迫去打了暴力,然而只得到,事实证明我的想法是正确的,但是太菜了。
这类问题就是对于个不同的命题,存在命题,若不成立,那么就必须成立(反之亦然)。
有三对夫妻要参加舞会,每对夫妻至少有一人参加,是否存在一种方案使得题意被满足。建立状态表示参加和不参加,已知有一些夫妻之间存在矛盾,例如不能同时存在,即参加()就不能参加(),就可以用建图和求强连通分量、或者是来解决建图后的问题。
根据以上性质,在这个例子中我们可以从的状态向的状态连一条有向边,再从的状态向的状态连另一条有向边,这样我们就成功地描述了事情之间的因果逻辑。
在建好图以后,怎么处理这样一条条的逻辑关系呢?我们可以使用求强联通分量的算法,为什么呢?因为这些逻辑关系实际上就是单向的“导致”,在一个强连通分量内,所有命题必须同时存在,如果有两个互斥的条件同时存在于一个中,那么就是不能满足的。
同理在这个题里,如果一个评委的要求不能被满足,那么要求就是必须被满足的,所以我们建边的方式就有了,之后的照着打就行了,只是多组数据记得该重置的要重置(特别是head[maxn]数组!!)
点击查看代码
| #include<cstdio> |
| #include<iostream> |
| #include<algorithm> |
| #include<bits/stdc++.h> |
| using namespace std; |
| const int maxn=2e4+10; |
| int n,m; |
| struct Edge{int u,v;int nex;}E[maxn]; |
| int tote=0;int head[maxn]; |
| void add(int u,int v) |
| { |
| E[++tote].u=u,E[tote].v=v; |
| E[tote].nex=head[u];head[u]=tote; |
| } |
| int dfn[maxn],low[maxn],cnt=0,s[maxn],ins[maxn],top; |
| int scc[maxn],num=0; |
| void tarjan(int x) |
| { |
| low[x]=dfn[x]=++cnt; |
| s[++top]=x;ins[x]=1; |
| for(int i=head[x];i;i=E[i].nex) |
| { |
| int v=E[i].v; |
| if(!dfn[v]) |
| { |
| tarjan(v); |
| low[x]=min(low[x], low[v]); |
| } |
| else if(ins[v]) |
| low[x]=min(low[x],dfn[v]); |
| } |
| if(dfn[x]==low[x]) |
| { |
| ++num; |
| while(s[top]!=x) |
| { |
| scc[s[top]]=num; |
| ins[s[top]]=0; |
| top--; |
| } |
| scc[s[top]]=num; |
| ins[s[top]]=0; |
| --top; |
| } |
| } |
| void reset() |
| { |
| memset(head,0,sizeof head); |
| memset(dfn,0,sizeof dfn); |
| memset(low,0,sizeof low); |
| memset(ins,0,sizeof ins); |
| memset(s,0,sizeof s); |
| top=tote=cnt=num=0; |
| } |
| inline int read() |
| { |
| int x=0,f=1; |
| char c=getchar(); |
| while('0'>c||c>'9') |
| { |
| if(c=='-')f=-1; |
| c=getchar(); |
| } |
| while('0'<=c&&c<='9') |
| { |
| x=(x<<1)+(x<<3)+(c^48); |
| c=getchar(); |
| } |
| return x*f; |
| } |
| |
| char tmp1[10],tmp2[10]; |
| void trans(char a1[],char a2[]) |
| { |
| int c1=0,c2=0; |
| for(int i=1;i<strlen(a1);i++) |
| c1=c1*10+a1[i]-'0'; |
| for(int i=1;i<strlen(a2);i++) |
| c2=c2*10+a2[i]-'0'; |
| add(a1[0]=='m'?c1+n:c1,a2[0]=='m'?c2:c2+n); |
| } |
| int main() |
| { |
| int t=read(); |
| while(t--) |
| { |
| reset(); |
| n=read(),m=read(); |
| for(int i=1;i<=m;i++) |
| { |
| scanf("%s %s",tmp1,tmp2); |
| trans(tmp1,tmp2),trans(tmp2,tmp1); |
| } |
| for(int i=1;i<=n*2;i++)if(!dfn[i])tarjan(i); |
| int flag=0; |
| for(int i=1;i<=n;i++) |
| { |
| if(scc[i]==scc[i+n]) |
| { |
| flag=1; |
| break; |
| } |
| } |
| if(flag==1)puts("BAD"); |
| else puts("GOOD"); |
| } |
| return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
###唯一小坑点:在建边的时候处理字母后面的数字时不能只处理一位,因为还可能存在三位数啥的
连一条有效边就让集合减少1个,初始n个,目标k个,所以只用连n-k个就行,连完了n-k个边后,下一个可以合并集合的边就是要求的答案了
点击查看代码
| #include<bits/stdc++.h> |
| using namespace std; |
| const int maxn=1e6+100; |
| double calc(double x1,double y1,double x2,double y2){return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));} |
| int n,k;int x,y; |
| struct Edge{int u,v;double w;}E[maxn];int tote; |
| struct point{int x;int y;}p[maxn]; |
| bool cmp(Edge a,Edge b){return a.w<b.w;} |
| int f[maxn]; |
| int find(int x){return x==f[x]?x:f[x]=find(f[x]);} |
| inline int read() |
| { |
| int x=0,f=1; |
| char c=getchar(); |
| while(c<'0'||c>'9') |
| { |
| if(c=='-')f=-1; |
| c=getchar(); |
| } |
| while('0'<=c&&c<='9') |
| { |
| x=(x<<1)+(x<<3)+(c^48); |
| c=getchar(); |
| } |
| return x*f; |
| } |
| int main() |
| { |
| freopen("group.in","r",stdin); |
| freopen("group.out","w",stdout); |
| scanf("%d%d",&n,&k); |
| for(int i=1;i<=n;i++)p[i].x=read(),p[i].y=read(),f[i]=i; |
| for(int i=1;i<=n;i++) |
| for(int j=i+1;j<=n;j++) |
| E[++tote].u=i,E[tote].v=j,E[tote].w=calc(p[i].x,p[i].y,p[j].x,p[j].y); |
| sort(E+1,E+tote+1,cmp); |
| int cnt=0;int flag=0; |
| for(int i=1;i<=tote;i++) |
| { |
| if(cnt==n-k) |
| { |
| flag=1; |
| } |
| int x=find(E[i].u),y=find(E[i].v); |
| if(x!=y) |
| { |
| cnt++; |
| if(flag==1) |
| { |
| printf("%.2lf",E[i].w); |
| break; |
| } |
| f[x]=y; |
| } |
| } |
| return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
题目是这样的,给出猎人、猎物、和障碍物,一个猎人在时刻时可以击杀一个猎物(前提是猎物在猎人的射程范围之内)。猎人有坐标、攻击半径、攻击冷却;猎物只有坐标;障碍物有坐标和半径,(其实就是一个圆)。现在求如果能击杀所有的猎物,求最小的击杀时间;或者不能全部击杀,就输出.
这道题是计算几何是非常明显的,很容易想到我们可以直接预处理出每个猎人能够击杀的猎物,然后再做后续的处理,那么我们如何预处理呢?考试的时候我想的是对于任何一个猎人点和障碍物圆,他们之间至多存在两条切线,那么我们只要算出这两条切线,就可以处理出当前能够被猎人打到的猎物,可惜我算不来切线啊。实际上不用这么,麻烦,画个图就很容易看出来
我们多多少少可以去考虑半径和垂线之间的长短关系,上图这种就是显然不行的

就像这样垂足在线段上面的时候,垂线半径都是可以的

那么垂足像这样不在线段上呢,,我们只用看两个端点到圆心的距离和半径的大小关系就行。
然后三层循环就可以预处理出所有猎人和猎物了。
但是很少有人能想到后续处理是网络最大流,当时我都想打爆搜了。。为什么是网络流呢?因为一个猎人可以击杀多个猎物,换句话说,一个猎物可以被多个猎人击杀,相当于所有猎人向猎物建了一条流量为的边,源点0向每个猎人建一条边,流量为当前总时间t内他们能够击杀的猎物数(时刻的时候也可以打一个),在从每个猎物向一个超级远点建边(只用被击杀一次)。那么现在要求的就是当前总时间下最大流是否为总猎物数,那么这个最大时间一定是具有单调性的(多了就可能会有剩余,少了就杀不完),那么对于这种单调性问题我们就可以采用二分来解决。每次只改变向每个猎人连出的边权,然后跑最大流直到答案收敛。
点击查看代码
| #include <bits/stdc++.h> |
| using namespace std; |
| const int maxn=500000; |
| int n,m,k,s,t; |
| int dis[maxn],vis[maxn],pre[maxn]; |
| int w[maxn],v[maxn],nex[maxn];int wx[maxn]; |
| struct witch{int x,y,r;int t;}a[maxn]; |
| struct Target{int x,y;}c[maxn]; |
| struct Tree{int x,y,r;}rt[maxn]; |
| int tote=1,head[maxn]; |
| inline void add(int uu,int vv,int ww) |
| { |
| v[++tote]=vv;w[tote]=ww;nex[tote]=head[uu];head[uu]=tote; |
| v[++tote]=uu;w[tote]=0;nex[tote]=head[vv];head[vv]=tote; |
| } |
| inline int bfs() |
| { |
| memset(vis,0,sizeof vis); |
| queue<int> q; |
| q.push(s); |
| vis[s]=1; |
| dis[s]=2005120700; |
| while(!q.empty()) |
| { |
| int x=q.front(); |
| q.pop(); |
| for(register int i=head[x];i;i=nex[i]) |
| { |
| if(w[i]==0) continue; |
| int vv=v[i]; |
| if(vis[vv]==1) continue; |
| dis[vv]=min(dis[x],w[i]); |
| pre[vv]=i; |
| q.push(vv); |
| vis[vv]=1; |
| if(vv==t) |
| return 1; |
| } |
| } |
| return 0; |
| } |
| int ans; |
| inline void update() |
| { |
| int x=t; |
| while(x!=s) |
| { |
| int vv=pre[x]; |
| w[vv]-=dis[t]; |
| w[vv^1]+=dis[t]; |
| x=v[vv^1]; |
| } |
| ans+=dis[t]; |
| } |
| void max_stream() |
| { |
| memset(dis,0,sizeof dis); |
| memset(pre,0,sizeof pre); |
| ans=0; |
| while(bfs()!=0) |
| update(); |
| } |
| inline int read() |
| { |
| int x=0,f=1; |
| char c=getchar(); |
| while(c<'0'||c>'9') |
| { |
| if(c=='-')f=-1; |
| c=getchar(); |
| } |
| while('0'<=c&&c<='9') |
| { |
| x=(x<<1)+(x<<3)+(c^48); |
| c=getchar(); |
| } |
| return x*f; |
| } |
| void input() |
| { |
| for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read(),a[i].r=read(),a[i].t=read(); |
| for(int i=1;i<=m;i++)c[i].x=read(),c[i].y=read(); |
| for(int i=1;i<=k;i++)rt[i].x=read(),rt[i].y=read(),rt[i].r=read(); |
| } |
| double calc(int x1,int y1,int x2,int y2){return sqrt(1.0*(x1-x2)*(x1-x2)+1.0*(y1-y2)*(y1-y2));} |
| bool jud(witch a,Target b,Tree c) |
| { |
| int A=b.y-a.y,B=a.x-b.x,C=b.x*a.y-a.x*b.y;double d; |
| double fx=(B*B*c.x-A*B*c.y)*1.0/(A*A+B*B),fy=(-A*B*c.x+A*A*c.y-B*C)*1.0/(A*A+B*B); |
| if(calc(a.x,a.y,fx,fy)+calc(fx,fy,b.x,b.y)==calc(a.x,a.y,b.x,b.y))d=min(calc(a.x,a.y,c.x,c.y),calc(b.x,b.y,c.x,c.y)); |
| |
| else d=abs(A*c.x+B*c.y+C)*1.0/sqrt(A*A+B*B); |
| return c.r<=d; |
| } |
| |
| int able[maxn]; |
| int rec[maxn],cur; |
| int main() |
| { |
| n=read(),m=read(),k=read(); |
| s=0,t=n+m+10; |
| input(); |
| for(int i=1;i<=n;i++) |
| { |
| for(int j=1;j<=m;j++) |
| { |
| if(calc(a[i].x,a[i].y,c[j].x,c[j].y)>=a[i].r)continue; |
| int flag=1; |
| for(int t=1;t<=k;t++) |
| if(!jud(a[i],c[j],rt[t]))flag=0; |
| if(flag) |
| { |
| able[j]=1; |
| add(i,j+n,1); |
| } |
| } |
| } |
| |
| for(int i=1;i<=n;i++)add(0,i,0),rec[++cur]=tote-1; |
| for(int i=1;i<=m;i++) |
| { |
| add(i+n,t,1); |
| if(!able[i]) |
| { |
| printf("-1"); |
| return 0; |
| } |
| } |
| int l=0,r=1<<25; |
| int fans=r; |
| while(l<=r) |
| { |
| int mid=(l+r)>>1; |
| memcpy(wx,w,sizeof wx); |
| for(int i=1;i<=cur;i++) |
| w[rec[i]]=mid/a[v[rec[i]]].t+1; |
| max_stream(); |
| if(ans==m)r=mid-1,fans=mid; |
| else l=mid+1; |
| memcpy(w,wx,sizeof w); |
| } |
| printf("%d",fans); |
| return 0; |
| } |
最后还是一些很琐碎的东西,比如我们需要一个数组来记录跑最大流之前的边权,跑完了再重新复制给原数组。还有就是每次最大流之前要清空记录流量的数组,还有数组。
芝士一道还没补完的题
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!