Codeforces 部分題目題解(口胡)
883D
題目大意:給你一個長度為n的字串,上面有牛(“P”),草(“*”)和空地(“.”)。現在你給每一頭牛規定一個方向,它會一直往前吃草,直到走到邊界。每一份草只會被吃1次,要求輸出最多吃多少草,以及在此基礎下吃完最後一份草的最小時間。n<=1000000。
做法:很明顯兩頭牛就可以吃完所有草,於是暴力處理0,1頭牛的情況。然後由於具有單調性,考慮二分答案後貪心(時限3s不虛)。接下來證明兩個小結論:
1.最前面的草,頂多會被它後面第二頭牛吃掉。
這個從上圖就可以看出。①中紅色和藍色的邊分別比②中紅色和藍色的邊長。
2.二分完閥值mid後,如果最前面的草後面mid處至少有兩頭牛,且第一頭牛與第二頭牛之間沒有草,那麼讓第一頭牛吃該草,第二頭牛往後吃。
這個結論應該很顯然……
但是如果兩頭牛中間有草怎麼辦呢?是不是讓第二頭牛往前吃,第一頭牛往後吃就最優了?答案是否定的。我一開始按照這個思路寫了個貪心,結果被下面這組資料卡掉了:
19
(這組資料的最優方案應該是讓第一,三頭牛往前吃,第二頭牛往後吃)
所以我們要用DP!記f[i]=j表示考慮完第i頭牛之後,最多能處理完1~j處的草。然後根據上述思路用f[i-2]或f[i-1]轉移即可。
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維護即可,時間複雜度
然而這樣做會被卡常(雖然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]的交集內,不存在則無解。這樣時間就是
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所在集合。時間複雜度為
(程式碼因特殊原因不貼出)。
lhxQAQ老賊喪天良,我與珂朵莉共存亡!!!
891C
題目大意:先給出一幅n個點,m條邊的圖(邊按輸入順序編號)。然後有q個詢問,每次詢問給出數字k,再給出k條邊的編號,問這k條邊能不能同時存在於這幅圖的最小生成樹上。可以則輸出”YES”,否則輸出”NO”(均不含引號)。
做法:一開始寫了個樹上倍增,想著隨便構一棵最小生成樹,然後看路徑最大值是否等於當前邊權就可以了。然而這樣連樣例都過不了,因為同一個詢問的邊有可能相互衝突……
正確的方法是將所有邊離線,按權值從小到大排序,權值相同則按所在詢問的編號從小到大。要知道屬於第i個詢問,權值為val的邊是否能存在於最小生成樹上,就要將權值為1~val-1的邊加進並查集裡,並將其它權值為val的,屬於第i個詢問的邊加進並查集,然後看兩點是否連通。如果下一條邊和當前邊權值相同但不屬於同一個詢問,為了保證正確性,需要將權值為val的,屬於第i個詢問的邊先退出並查集。時間複雜度
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,它有
做法:一道有點腦洞的構造題。
將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]。
做法:一開始想錯了貪心,導致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;
}
(持續待更)