883D
题目大意:给你一个长度为n的字符串,上面有牛(“P”),草(“*”)和空地(“.”)。现在你给每一头牛规定一个方向,它会一直往前吃草,直到走到边界。每一份草只会被吃1次,要求输出最多吃多少草,以及在此基础下吃完最后一份草的最小时间。n<=1000000。
做法:很明显两头牛就可以吃完所有草,于是暴力处理0,1头牛的情况。然后由于具有单调性,考虑二分答案后贪心(时限3s不虚)。接下来证明两个小结论:
1.最前面的草,顶多会被它后面第二头牛吃掉。
这个从上图就可以看出。①中红色和蓝色的边分别比②中红色和蓝色的边长。
2.二分完阀值mid后,如果最前面的草后面mid处至少有两头牛,且第一头牛与第二头牛之间没有草,那么让第一头牛吃该草,第二头牛往后吃。
这个结论应该很显然……
但是如果两头牛中间有草怎么办呢?是不是让第二头牛往前吃,第一头牛往后吃就最优了?答案是否定的。我一开始按照这个思路写了个贪心,结果被下面这组数据卡掉了:
19
∗∗P.∗..∗..P..∗.∗P∗∗
(这组数据的最优方案应该是让第一,三头牛往前吃,第二头牛往后吃)
所以我们要用DP!记f[i]=j表示考虑完第i头牛之后,最多能处理完1~j处的草。然后根据上述思路用f[i-2]或f[i-1]转移即可。
我一开始把时限看成了1s,结果想了很久都想不出O(n)的做法QAQ
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=1000100;
int sum[maxn];
int f[maxn];
int id[maxn];
char s[maxn];
int a[maxn];
int n;
int cnt=0,num=0;
bool Judge(int x)
{
f[0]=0;
for (int i=1; i<=num; i++)
{
f[i]=0;
int y=id[i];
if (f[i-1]>=y-1) f[i]=max(f[i],y+x);
if (f[i-1]<y-1)
if (sum[y]-sum[ f[i-1] ])
if (sum[max(0,y-x-1)]-sum[ f[i-1] ]>0) return false;
else
{
f[i]=max(f[i],y);
if ( i>=2 && sum[max(0,y-x-1)]-sum[ f[i-2] ]<=0 )
f[i]=max(f[i],id[i-1]+x);
}
else f[i]=max(f[i],y+x);
}
if ( f[num]>=n || sum[n]-sum[ f[num] ]<=0 ) return true;
return false;
}
int Binary()
{
int L=0,R=n;
while (L+1<R)
{
int mid=(L+R)>>1;
if ( Judge(mid) ) R=mid;
else L=mid;
}
return R;
}
int main()
{
freopen("2326.in","r",stdin);
freopen("2326.out","w",stdout);
scanf("%d",&n);
scanf("%s",s);
for (int i=1; i<=n; i++)
{
if (s[i-1]=='*') a[i]=0,cnt++;
if (s[i-1]=='P') a[i]=1,id[++num]=i;
if (s[i-1]=='.') a[i]=2;
}
if (num<=1)
if (num==0) printf("0 0\n");
else
{
int x=0,y=0;
for (int i=1; i<=n; i++)
if (a[i]==1) x=i;
for (int i=1; i<=x; i++)
if (a[i]==0) y++;
int Le=0,Ri=0;
for (int i=x; i>=1; i--)
if (a[i]==0) Le=x-i;
for (int i=x; i<=n; i++)
if (a[i]==0) Ri=i-x;
if (y>cnt-y) printf("%d %d\n",y,Le);
else
if (y<cnt-y) printf("%d %d\n",cnt-y,Ri);
else
if (Le<Ri) printf("%d %d\n",y,Le);
else printf("%d %d\n",cnt-y,Ri);
}
else
{
sum[0]=0;
for (int i=1; i<=n; i++)
{
sum[i]=sum[i-1];
if (a[i]==0) sum[i]++;
}
int ans=Binary();
printf("%d %d\n",cnt,ans);
}
return 0;
}
875E
题目大意:有n个城市,给出它们在数轴上的坐标。现在有两个人在s1和s2处,他们要按顺序走完这n个城市,求他们两个人最大距离的最小值。n<=100000。
做法:分析之后发现,它就是要你把这n个城市分成若干段,使得每一段的所有城市到上一段的最后一个城市的距离小于等于ans。二分答案之后用treap维护即可,时间复杂度
O(nlog2(n))
。
然而这样做会被卡常(虽然CF的机子跑得灰常快)。
更优的方法是从后往前考虑。如果第n-1个城市在[a[n]-mid,a[n]+mid]的范围内,就可以无视掉第n个城市;否则就要求第n-2个城市在[a[n]-mid,a[n]+mid]与[a[n-1]-mid,a[n-1]+mid]的交集内,不存在则无解。这样时间就是
O(nlog(n))
。
CODE(Treap):
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100100;
const int oo=2000001000;
const long long M1=998244353;
const long long M2=1000000007;
const long long M3=1333333331;
typedef long long LL;
LL seed;
struct Tnode
{
int val,id,min_id,fix;
Tnode *lson,*rson;
} tree[maxn];
Tnode *Root;
int cur;
int Right[maxn];
int a[maxn];
int n,s1,s2;
int Rand()
{
seed=(seed*M1+M2)%M3;
return seed;
}
Tnode *New_node(int Val,int Id)
{
cur++;
tree[cur].val=Val;
tree[cur].min_id=tree[cur].id=Id;
tree[cur].fix=Rand();
tree[cur].lson=tree[cur].rson=NULL;
return tree+cur;
}
void Recount(Tnode *P)
{
P->min_id=P->id;
if (P->lson) P->min_id=min(P->min_id,P->lson->min_id);
if (P->rson) P->min_id=min(P->min_id,P->rson->min_id);
}
void Right_turn(Tnode *&P)
{
Tnode *W=P->lson;
P->lson=W->rson;
W->rson=P;
P=W;
Recount(P->rson);
Recount(P);
}
void Left_turn(Tnode *&P)
{
Tnode *W=P->rson;
P->rson=W->lson;
W->lson=P;
P=W;
Recount(P->lson);
Recount(P);
}
void Insert(Tnode *&P,int Val,int Id)
{
if (!P) P=New_node(Val,Id);
else
if ( Val<P->val || ( Val==P->val && Id<P->id ) )
{
Insert(P->lson,Val,Id);
if ( P->lson->fix < P->fix ) Right_turn(P);
else Recount(P);
}
else
{
Insert(P->rson,Val,Id);
if ( P->rson->fix < P->fix ) Left_turn(P);
else Recount(P);
}
}
int Find_succ(Tnode *P,int Val,int Ans)
{
if (!P) return Ans;
if (Val<P->val)
{
Ans=min(Ans,P->id);
if (P->rson) Ans=min(Ans,P->rson->min_id);
return Find_succ(P->lson,Val,Ans);
}
return Find_succ(P->rson,Val,Ans);
}
int Find_prev(Tnode *P,int Val,int Ans)
{
if (!P) return Ans;
if (P->val<Val)
{
Ans=min(Ans,P->id);
if (P->lson) Ans=min(Ans,P->lson->min_id);
return Find_prev(P->rson,Val,Ans);
}
return Find_prev(P->lson,Val,Ans);
}
bool Judge(int x)
{
Root=NULL;
cur=-1;
Insert(Root,-oo,n+1);
Insert(Root,oo,n+1);
for (int i=n; i>=1; i--)
{
int Succ=Find_succ(Root,a[i]+x,n+1);
int Prev=Find_prev(Root,a[i]-x,n+1);
Right[i]=min(Prev,Succ)-1;
Insert(Root,a[i],i);
}
int now=0;
int Succ=Find_succ(Root,s1+x,n+1);
int Prev=Find_prev(Root,s1-x,n+1);
now=max(now, min(Prev,Succ)-1 );
Succ=Find_succ(Root,s2+x,n+1);
Prev=Find_prev(Root,s2-x,n+1);
now=max(now, min(Prev,Succ)-1 );
for (int i=1; i<=n; i++)
{
if (i>now) return false;
now=max(now,Right[i]);
}
return true;
}
int Binary()
{
int L=0,R=1000000000;
while (L+1<R)
{
int mid=(L+R)>>1;
if ( Judge(mid) ) R=mid;
else L=mid;
}
return R;
}
int main()
{
freopen("2330.in","r",stdin);
freopen("2330.out","w",stdout);
scanf("%d%d%d",&n,&s1,&s2);
seed=n;
for (int i=1; i<=n; i++) scanf("%d",&a[i]);
int ans=Binary();
ans=max(ans, max(s1-s2,s2-s1) );
printf("%d\n",ans);
return 0;
}
CODE(区间交):
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=100100;
int a[maxn];
int n,s1,s2;
bool Judge(int x)
{
int L=a[n]-x,R=a[n]+x;
for (int i=n-1; i>=1; i--)
{
if ( L<=a[i] && a[i]<=R ) L=a[i]-x,R=a[i]+x;
else
{
L=max(L,a[i]-x);
R=min(R,a[i]+x);
if (L>R) return false;
}
}
if ( L<=s1 && s1<=R ) return true;
if ( L<=s2 && s2<=R ) return true;
return false;
}
int Binary()
{
int L=0,R=1000000000;
while (L+1<R)
{
int mid=(L+R)>>1;
if ( Judge(mid) ) R=mid;
else L=mid;
}
return R;
}
int main()
{
freopen("2330.in","r",stdin);
freopen("2330.out","w",stdout);
scanf("%d%d%d",&n,&s1,&s2);
for (int i=1; i<=n; i++) scanf("%d",&a[i]);
int ans=Binary();
ans=max(ans, max(s1-s2,s2-s1) );
printf("%d\n",ans);
return 0;
}
875F
题目大意:给出m个三元组(a,b,c),表示如果该组选择了a或b两个数中的一个,你就会获得c的报酬。每个数顶多属于一个组,每个组顶多选择一个数。要求最大化报酬和。a,b<=n,n,m<=200000。
做法:这就是道典型的二分图匹配嘛
将所有三元组按c从大到小排序,然后按顺序处理。对于第i个三元组,先查看ai和bi是否在同一个集合,是的话再看这个集合是否已经有一个环,有环则选不了;不在一个集合,就看两个集合是否都有环,都有环则选不了,否则获得c的贡献,然后合并ai和bi所在集合。时间复杂度为
O(nα(n))
。
(代码因特殊原因不贴出)。
lhxQAQ老贼丧天良,我与珂朵莉共存亡!!!
891C
题目大意:先给出一幅n个点,m条边的图(边按输入顺序编号)。然后有q个询问,每次询问给出数字k,再给出k条边的编号,问这k条边能不能同时存在于这幅图的最小生成树上。可以则输出”YES”,否则输出”NO”(均不含引号)。 n,m,q,∑k<=5∗105 。
做法:一开始写了个树上倍增,想着随便构一棵最小生成树,然后看路径最大值是否等于当前边权就可以了。然而这样连样例都过不了,因为同一个询问的边有可能相互冲突……
正确的方法是将所有边离线,按权值从小到大排序,权值相同则按所在询问的编号从小到大。要知道属于第i个询问,权值为val的边是否能存在于最小生成树上,就要将权值为1~val-1的边加进并查集里,并将其它权值为val的,属于第i个询问的边加进并查集,然后看两点是否连通。如果下一条边和当前边权值相同但不属于同一个询问,为了保证正确性,需要将权值为val的,属于第i个询问的边先退出并查集。时间复杂度
O(nlog(n))
。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=500100;
struct edge
{
int u,v,w;
} e[maxn];
struct data
{
int a,b,val,id;
} ask[maxn];
int cur=0;
int fa[maxn];
int Size[maxn];
int sak[maxn];
int tail=0;
bool ans[maxn];
int n,m,q;
bool Comp1(edge x,edge y)
{
return x.w<y.w;
}
bool Comp2(data x,data y)
{
return x.val<y.val || ( x.val==y.val && x.id<y.id );
}
int Find(int x)
{
if (fa[x]==x) return x;
return Find(fa[x]);
}
void Add(int x,int y)
{
x=Find(x);
y=Find(y);
if (x==y) return;
if (Size[x]<Size[y]) swap(x,y);
fa[y]=x;
Size[x]+=Size[y];
}
void Clear()
{
while (tail)
{
int x=sak[tail];
Size[ fa[x] ]-=Size[x];
fa[x]=x;
tail--;
}
}
void Push(int x,int y)
{
if (Size[x]<Size[y]) swap(x,y);
fa[y]=x;
Size[x]+=Size[y];
sak[++tail]=y;
}
int main()
{
freopen("2341.in","r",stdin);
freopen("2341.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1; i<=m; i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
scanf("%d",&q);
for (int i=1; i<=q; i++)
{
int k;
scanf("%d",&k);
for (int j=1; j<=k; j++)
{
int x;
scanf("%d",&x);
cur++;
ask[cur].a=e[x].u;
ask[cur].b=e[x].v;
ask[cur].val=e[x].w;
ask[cur].id=i;
}
ans[i]=1;
}
sort(e+1,e+m+1,Comp1);
sort(ask+1,ask+cur+1,Comp2);
for (int i=1; i<=n; i++) fa[i]=i,Size[i]=1;
int h1=1,h2=1;
while (h2<=cur)
{
Clear();
while ( e[h1].w<ask[h2].val && h1<=m ) Add(e[h1].u,e[h1].v),h1++;
int t2=h2;
while ( t2<=cur && ask[t2].val==ask[h2].val )
{
if (ask[t2].id!=ask[t2-1].id) Clear();
int x=Find(ask[t2].a);
int y=Find(ask[t2].b);
if (x==y) ans[ ask[t2].id ]=0;
else Push(x,y);
t2++;
}
h2=t2;
}
for (int i=1; i<=q; i++)
if (ans[i]) printf("YES\n");
else printf("NO\n");
return 0;
}
891B
题目大意:给出一个长度为n的序列a,它有 2n−2 个非空真子集。现要求将a中的数打乱,构造出序列b,使得b的这些子集的对应位置之和与a的对应位置之和都不等。n<=22。a中的数互不相同。
做法:一道有点脑洞的构造题。
将a中的数从小到大排序,然后全体右移一位,最小的数变成最大。这样不包含原先最小数的集合,每个的和都变小了;包含了原先最小数的集合,考虑其补集,补集的和一定变小,所以该集合之和一定变大。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=30;
struct data
{
int val,id;
} a[maxn];
int n;
bool Comp1(data x,data y)
{
return x.val<y.val;
}
bool Comp2(data x,data y)
{
return x.id<y.id;
}
int main()
{
freopen("2342.in","r",stdin);
freopen("2342.out","w",stdout);
scanf("%d",&n);
for (int i=1; i<=n; i++) scanf("%d",&a[i].val),a[i].id=i;
sort(a+1,a+n+1,Comp1);
a[n+1].val=a[1].val;
for (int i=1; i<=n; i++) a[i].val=a[i+1].val;
sort(a+1,a+n+1,Comp2);
for (int i=1; i<=n; i++) printf("%d ",a[i]);
printf("\n");
return 0;
}
883B
题目大意:有n名军人,军衔等级从低到高为1到k中的整数。给你m对关系(xi,yi),表示要满足军人xi的军衔高于军人yi的军衔。已知其中一些军人的军衔,求出任意一种所有军人可能的军衔的方案,并且每种军衔都至少存在一名军人,如无法满足条件输出“-1”。一开始给定数组r,r[i]不为0表示已知第i位军人的军衔为r[i]。 n,m,k<=2∗105 。
做法:一开始想错了贪心,导致WA了好多发,最后看了DLee大佬的代码才明白。
先将大小关系构出一个图,如果有环则无解。然后正反向拓扑一遍就可以知道每一个人军衔的下限Min与上限Max。现在的问题就变成了如何用这些区间覆盖1~k。我们从小到大考虑下限。假设当前做到i,则用一个堆维护所有Min<=i的区间的Max,每一次取出一个Max最小的来覆盖i。但如果堆里最小的Max都等于i,则令其答案一定要等于i。
接下来我们发现一个点的答案确定了之后,它所到达的点的Min会改变(变为当前点的答案+1)。于是干脆一开始先不要算出Min,边做边算,用一个链表存Min为某个值的人有哪些即可。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=200100;
struct edge
{
int obj;
edge *Next;
} e[maxn*3];
edge *head[maxn];
edge *nhead[maxn];
int cur=-1;
int Heap[maxn];
int tail=0;
int Min[maxn];
int Max[maxn];
int ans[maxn];
int pin[maxn];
int pout[maxn];
int vis[maxn];
int que[maxn];
int he,ta;
int r[maxn];
int n,m,k;
void Add(edge **Head,int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=Head[x];
Head[x]=e+cur;
}
void Dfs(int node)
{
vis[node]=1;
for (edge *p=head[node]; p; p=p->Next)
{
int to=p->obj;
if (vis[to]==1)
{
printf("-1\n");
exit(0);
}
if (!vis[to]) Dfs(to);
}
vis[node]=2;
}
void Calc_Max()
{
he=0,ta=1;
for (int i=1; i<=n; i++)
{
Max[i]=k;
if (!pout[i]) que[++ta]=i;
if (r[i]) Max[i]=r[i];
}
while (he<ta)
{
int node=que[++he];
for (edge *p=nhead[node]; p; p=p->Next)
{
int to=p->obj;
Max[to]=min(Max[to],Max[node]-1);
pout[to]--;
if (!pout[to])
{
que[++ta]=to;
if ( r[to]>Max[to] && r[to] )
{
printf("-1\n");
exit(0);
}
if (r[to]) Max[to]=r[to];
}
}
}
}
void Insert(int x)
{
Heap[++tail]=x;
x=tail;
while (x>1)
{
int y=x>>1;
if ( Max[ Heap[y] ]<Max[ Heap[x] ] ) break;
swap(Heap[x],Heap[y]);
x=y;
}
}
int Delete()
{
int temp=Heap[1];
Heap[1]=Heap[tail];
tail--;
int x=1;
while (1)
{
int y=x,Left=x<<1,Right=Left|1;
if ( Left<=tail && Max[ Heap[Left] ]<Max[ Heap[y] ] ) y=Left;
if ( Right<=tail && Max[ Heap[Right] ]<Max[ Heap[y] ] ) y=Right;
if (x==y) break;
swap(Heap[x],Heap[y]);
x=y;
}
return temp;
}
void Update(int node)
{
for (edge *p=head[node]; p; p=p->Next)
{
int to=p->obj;
pin[to]--;
Min[to]=max(Min[to],ans[node]+1);
if (!pin[to])
{
if ( r[to]<Min[to] && r[to] )
{
printf("-1\n");
exit(0);
}
if (r[to]) Min[to]=r[to];
Add(nhead,Min[to],to);
}
}
}
int main()
{
freopen("2345.in","r",stdin);
freopen("2345.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for (int i=1; i<=n; i++) scanf("%d",&r[i]),head[i]=nhead[i]=NULL;
for (int i=1; i<=m; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(head,y,x);
pin[x]++;
pout[y]++;
Add(nhead,x,y);
}
for (int i=1; i<=n; i++)
if (!vis[i]) Dfs(i);
Calc_Max();
for (int i=1; i<=n; i++)
if (Max[i]<1)
{
printf("-1\n");
return 0;
}
for (int i=1; i<=k; i++) nhead[i]=NULL;
for (int i=1; i<=n; i++)
if (!pin[i])
{
Min[i]=1;
if (r[i]) Min[i]=r[i];
Add(nhead,Min[i],i);
}
for (int i=1; i<=k; i++)
{
for (edge *p=nhead[i]; p; p=p->Next) Insert(p->obj);
if (!tail)
{
printf("-1\n");
return 0;
}
int x=Delete();
ans[x]=i;
Update(x);
while ( tail && Max[ Heap[1] ]==i )
x=Delete(),ans[x]=i,Update(x);
}
for (int i=1; i<=n; i++)
if (!ans[i])
{
printf("-1\n");
return 0;
}
for (int i=1; i<=n; i++) printf("%d ",ans[i]);
printf("\n");
return 0;
}
(持续待更)