20220921祭
20220921(小寄)
t1 [JSOI2010]满汉全席
最初思路
第一眼看jio得是建图。建了半天图之后发现是错误的,果断放弃建图,然后开始打表。啊,不是打表,只是把数据放在表里。然后就感觉这道题是可以用dp硬转移的。dp[i]表示前i种材料能满足的最大评审员人数,用v数组存下到第i种材料的评审员编号。存下每种材料样式的评审员,枚举n种材料,每次暴力从i以前的状态转移过来。最后取得了40分的好成绩。
附40分代码:
点击查看代码
#include<iostream>
#include<cstring>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=105,M=1005;
int k,n,m;
char c;
int le,kp;
struct player{
int pos[M],cnt;
}p[N][2];
int v[N][M];
int dp[N],ppkp,ko,ok;
inline int change(char c){return c=='h'?0:1;}
inline int check(int i,int j){
int ans=0,sum=0;
fo(kkk,1,p[i][0].cnt){
if(!v[j][p[i][0].pos[kkk]])++ans;
}
fo(kkk,1,p[i][1].cnt){
if(!v[j][p[i][1].pos[kkk]])++sum;
}
ans>sum?kp=0:kp=1;
return max(ans,sum);
}
inline void merge(int i,int j,int g){
fo(kkk,1,m)
v[i][kkk]=v[j][kkk];
fo(kkk,1,p[i][g].cnt){
if(!v[i][p[i][g].pos[kkk]])v[i][p[i][g].pos[kkk]]=1;
}
}
int main(){
in(k);
while(k--){
scanf("%d%d",&n,&m);
memset(v,0,sizeof(v));
memset(p,0,sizeof(p));
memset(dp,0,sizeof(dp));
fo(i,1,m){
cin>>c;
kp=change(c);
cin>>le;
p[le][kp].pos[++p[le][kp].cnt]=i;
cin>>c;
kp=change(c);
cin>>le;
p[le][kp].pos[++p[le][kp].cnt]=i;
}
fo(i,1,n){
fo(j,0,i-1){
ppkp=check(i,j);
if(dp[i]<dp[j]+ppkp){
dp[i]=dp[j]+ppkp;
ko=j;
ok=kp;
}
}
merge(i,ko,ok);
}
if(dp[n]>=m)puts("GOOD");
else puts("BAD");
}
return 0;
}
正确思路
建图,2-sat。
AC_code
#include<iostream>
#include<cmath>
#include<cstring>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
template<typename T>inline void out(T x){
if(x<0)x=~x+1,putchar('-');
if(x>9)out(x/10);
putchar(x%10^48);
}
const int N=20005,M=200005;
int k,n,m;
char c,w;
int kp1,kp2;
struct edge{
int v,nex;
}e[M];
int head[N],tot;
struct stack{
int val[N];
int top;
stack (){top=0;memset(val,0,sizeof(val));}
void push(int x){
val[++top]=x;
}
void pop(){
--top;
}
int get(){
return val[top];
}
void clear(){
top=0;
}
}s;
inline void add(int u,int v){
e[++tot].v=v;
e[tot].nex=head[u];
head[u]=tot;
}
int low[N],dfn[N],cnt,ins[N],cn_t,group[N];
inline void tarjan(int x){
low[x]=dfn[x]=++cnt;
ins[x]=1,s.push(x);
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(low[x]==dfn[x]){
int v;++cn_t;
do{
v=s.get();
s.pop();
ins[v]=0;
group[v]=cn_t;
}while(v!=x);
}
}
bool f;
inline void init(){
tot=0;
f=0;
memset(head,0,sizeof(head));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(ins,0,sizeof(ins));
cnt=0,cn_t=0;
memset(group,0,sizeof(group));
s.clear();
}
int main(){
in(k);
while(k--){
in(n),in(m);
init();
fo(i,1,m){
cin>>c;
scanf("%d",&kp1);
cin>>w;
scanf("%d",&kp2);
if(c=='h'){
if(w=='h')add(kp1+n,kp2),add(kp2+n,kp1);
else add(kp1+n,kp2+n),add(kp2,kp1);
}
else{
if(w=='m')add(kp1,kp2+n),add(kp2,kp1+n);
else add(kp1,kp2),add(kp2+n,kp1+n);
}
}
for(int i=1;i<=n<<1;++i)
if(!dfn[i])tarjan(i);
for(int i=1;i<=n;++i)
if(group[i]==group[i+n]){
f=1;
break;
}
if(f)puts("BAD");
else puts("GOOD");
}
return 0;
}
t2 [JSOI2010]部落划分
心路历程
哇!这道题怎么做?思考ing。。。emmm,做不来,摆了。。。
还是打个暴力吧。暴力怎么打?不知道,反正先把各个点之间的距离算出来。咦?他要求部落间最短距离最远。那我们是不是可以用堆维护?可以,但是并没有什么用。。。因为两个点合并之后会导致部落间距离改变。所以思考会造成什么影响,可以发现,若a点到c点距离为1,b点到c点距离为2,则a,b两点合并后形成的部落到c点的距离为1。即一个部落到一个不属于该部落的点的距离为该部落所有点到该点的距离最小值。由此,我们大致可以发现,只需求能将所有点连接起来的最小路径,其实就是最小生成树。在保证存在k个部落时最小生成树上未被加入部落的边即为所求=)
点击查看代码
#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long
#define fo(i,x,y) for(int i=x;i<=y;++i)
using namespace std;
template<typename T>inline void in(T &x){
x=0;int f=0;char c=getchar();
for(;!isdigit(c);c=getchar())f|=(c=='-');
for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
x=f?-x:x;
}
const int N=1005;
int n,k;
struct point{
int x,y;
}p[N];
struct edge{
int u,v;
double w;
}e[1000005];
int tot;
inline double mlen(int a,int b){
return sqrt(pow((p[a].x-p[b].x),2)+pow((p[a].y-p[b].y),2));
}
int fa[N];
inline int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int num;
inline bool cmp(edge a,edge b){
return a.w<b.w;
}
bool f;
int main(){
in(n),in(k);
fo(i,1,n){
in(p[i].x),in(p[i].y);
}
for(int i=1;i<=n;i++){
fa[i]=i;
for(int j=1;j<i;j++){
tot++;
e[tot].u=i,e[tot].v=j;
e[tot].w=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
}
}
sort(e+1,e+tot+1,cmp);
fo(i,1,tot){
if(num==(n-k))f=1;
if(find(e[i].u)!=find(e[i].v)){
fa[find(e[i].v)]=find(e[i].u);
++num;
if(f){
printf("%.2lf",e[i].w);
return 0;
}
}
}
return 0;
}
t3 [JSOI2010]冷冻波
思路
预处理:每个巫妖所能杀死的小精灵。
若一个小精灵在某巫妖攻击范围内,且未被树木阻挡,则该巫妖可以击杀该小精灵。若存在无法杀死的小精灵,直接输出-1。
网络流:
可以发现巫妖与小精灵满足二分图的性质。用起点与各巫妖建边,再将巫妖与其可杀死的小精灵建边,最后将小精灵与汇点建边。小精灵与汇点的边权为1并且巫妖与小精灵的边权也为1,表示每个小精灵最多被杀1次。可以发现,在一定的时间以上,所有小精灵都是可以被杀死的,所以我们可以二分时间,那么每个污妖可以杀死的小精灵数即为\frac{所得时间}{攻击间隔}+1,那么我们就将起点与污妖的边权变为该值来表示。最后跑一遍最大流即可。