Test 2022.09.21

今天是JSOI2010专场

T1满汉全席(2-SAT

比较生疏的知识点,当时看着一眼就觉得是图论,甚至想到了最大流,但是因为建不来图,被迫去打了暴力,然而只得到10pts,事实证明我的想法是正确的,但是太菜了。

关于2SAT

这类问题就是对于n个不同的命题,存在命题a,b,若a不成立,那么b就必须成立(反之亦然)。

举个例子

有三对夫妻ai,bi要参加舞会,每对夫妻至少有一人参加,是否存在一种方案使得题意被满足。建立状态0,1表示参加和不参加,已知有一些夫妻之间存在矛盾,例如a1,b2不能同时存在,即a1参加(1b2就不能参加(0),就可以用建图和tarjan求强连通分量、或者是dfs来解决建图后的问题。
根据以上性质,在这个例子中我们可以从a11状态向b20状态连一条有向边,再从a10状态向b21状态连另一条有向边,这样我们就成功地描述了事情之间的因果逻辑。

解决问题

在建好图以后,怎么处理这样一条条的逻辑关系呢?我们可以使用tarjan求强联通分量的算法,为什么呢?因为这些逻辑关系实际上就是单向的“导致”,在一个强连通分量内,所有命题必须同时存在,如果有两个互斥的条件同时存在于一个SCC中,那么就是不能满足的。
同理在这个题里,如果一个评委的要求1不能被满足,那么要求2就是必须被满足的,所以我们建边的方式就有了,之后的tarjan照着打就行了,只是多组数据记得该重置的要重置(特别是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; // 结点 i 所在 SCC 的编号、当前的scc个数
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;
}
//1~n 'm' n+1~2n 'h'
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;
}
/*
2
3 4
m3 h1
m1 m2
h1 h3
h3 m2
2 4
h1 m2
m2 m1
h1 h2
m1 h2
*/
###唯一小坑点:在建边的时候处理字母后面的数字时不能只处理一位,因为还可能存在三位数啥的

T2部落划分

一眼最小生成树(传统艺能了)

连一条有效边就让集合减少1个,初始n个,目标k个,所以只用连n-k个就行,连完了n-k个边后,下一个可以合并集合的边就是要求的答案了

点击查看代码

T3冷冻波

题目是这样的,给出猎人、猎物、和障碍物,一个猎人在0时刻时可以击杀一个猎物(前提是猎物在猎人的射程范围之内)。猎人有坐标、攻击半径、攻击冷却;猎物只有坐标;障碍物有坐标和半径,(其实就是一个圆)。现在求如果能击杀所有的猎物,求最小的击杀时间;或者不能全部击杀,就输出1.

分析

这道题是计算几何是非常明显的,很容易想到我们可以直接预处理出每个猎人能够击杀的猎物,然后再做后续的处理,那么我们如何预处理呢?考试的时候我想的是对于任何一个猎人点和障碍物圆,他们之间至多存在两条切线,那么我们只要算出这两条切线,就可以处理出当前能够被猎人打到的猎物,可惜我算不来切线啊。实际上不用这么,麻烦,画个图就很容易看出来image
我们多多少少可以去考虑半径和垂线之间的长短关系,上图这种就是显然不行的
image
就像这样垂足在线段上面的时候,垂线半径都是可以的
image
那么垂足像这样不在线段上呢,Obviously,我们只用看两个端点到圆心的距离和半径的大小关系就行。
然后三层循环就可以预处理出所有猎人和猎物了。
但是很少有人能想到后续处理是网络最大流,当时我都想打爆搜了。。为什么是网络流呢?因为一个猎人可以击杀多个猎物,换句话说,一个猎物可以被多个猎人击杀,相当于所有猎人向猎物建了一条流量为1的边,源点0向每个猎人建一条边,流量为当前总时间t内他们能够击杀的猎物数T/cd[i]+10时刻的时候也可以打一个),在从每个猎物向一个超级远点建边(只用被击杀一次)。那么现在要求的就是当前总时间下最大流是否为总猎物数,那么这个最大时间一定是具有单调性的(多了就可能会有剩余,少了就杀不完),那么对于这种单调性问题我们就可以采用二分来解决。每次只改变0向每个猎人连出的边权,然后跑最大流直到答案收敛。

Code

点击查看代码

注意

最后还是一些很琐碎的东西,比如我们需要一个数组来记录跑最大流之前的边权,跑完了再重新复制给原数组。还有就是每次最大流之前要清空记录流量的dis[i]数组,还有pre[i]数组。

T4蔬菜庆典

芝士一道还没补完的题

posted @   Hanggoash  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
动态线条
动态线条end
点击右上角即可分享
微信分享提示