AtCoder Grand Contest 011
AtCoder Grand Contest 011
upd:這篇咕了好久,前面幾題是三周以前寫的。。。
AtCoder Grand Contest 011
A - Airport Bus
翻譯
有\(n\)個乘客到達了飛機場,現在他們都要坐車離開機場。第\(i\)個乘客到達的時間是\(T_i\),一個乘客必須在\([T_i,T_i+k]\)時刻做到車,否則他會生氣。一輛車最多可以坐\(C\)個人。問最少安排幾輛車可以讓所有人都不生氣。
題解
從前往後貪心即可。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; #define MAX 100100 inline int read() { int x=0;bool t=false;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } int n,C,K,T[MAX],ans,s,t; int main() { n=read();C=read();K=read(); for(int i=1;i<=n;++i)T[i]=read(); sort(&T[1],&T[n+1]); for(int i=1;i<=n;++i) if(!s)++ans,s=1,t=T[i]; else { if(s==C)++ans,s=1,t=T[i]; else { if(T[i]-t>K)++ans,s=1,t=T[i]; else ++s; } } printf("%d\n",ans); return 0; }
B - Colorful Creatures
翻譯
見洛谷
題解
顯然不合法的在排序之後是一段前綴,那麽倒推哪些合法即可。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; #define ll long long #define MAX 100100 inline int read() { int x=0;bool t=false;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } int n,ans=1;ll a[MAX],s; int main() { n=read(); for(int i=1;i<=n;++i)a[i]=read(); sort(&a[1],&a[n+1]); for(int i=1;i<=n;++i)a[i]+=a[i-1]; for(int i=n-1;i;--i) if(a[i]*2>=a[i+1]-a[i])++ans; else break; printf("%d\n",ans); return 0; }
C - Squared Graph
翻譯
給定一個\(n\)個點\(m\)條邊的圖,構建一個\(n^2\)個點的圖,新圖的每個點都可以看成一個二元組,新圖上的點\((a,b)\)和\((a',b')\)之間有邊,當且僅當原圖中\((a,a'),(b,b')\)之間有邊,問新圖的聯通塊個數。
題解
不太會,抄了一遍代碼,從這裏抄的
大概理解一下就是,首先考慮所有二元組很不好考慮,拆分行列,考慮情況。首先把所有沒有連上的單點全部拿出來把貢獻直接算好,接下來只會考慮所有存在邊的聯通塊。考慮一下如果兩個點\((a,b),(x,y)\)在新圖中連在一起,那麽必定在原圖中存在一條\(a\rightarrow x,b\rightarrow y\)
如果一個聯通塊是一個二分圖,那麽它自身就會貢獻\(2\)的答案,即把二分圖黑白染色之後左右分開,顯然把兩邊的點分別放在二元組的前面都會形成一個聯通塊;否則會貢獻\(1\)的答案。然後考慮任選兩個不連通的圖,他們之間的邊的關系都可以在新圖之中構成一個新的聯通塊,所以把貢獻加上就好了。
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 100100
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;}e[MAX<<2];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
bool fl;int sum;
int dep[MAX];bool vis[MAX];
int n,m;ll ans;
void dfs(int u,int ff)
{
vis[u]=true;++sum;
for(int i=h[u];i;i=e[i].next)
if(vis[e[i].v]&&e[i].v!=ff)fl|=dep[u]==dep[e[i].v];
else if(!vis[e[i].v])dep[e[i].v]=dep[u]^1,dfs(e[i].v,u);
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
int node=0,t1=0,t2=0;
for(int i=1;i<=n;++i)
{
if(vis[i])continue;
fl=false;sum=0;dfs(i,0);
if(sum==1)ans+=n*2-1,++node;
else if(!fl)ans+=2,++t1;
else ++ans,++t2;
}
ans-=1ll*(node-1)*node;
ans+=1ll*t1*(t1-1)*2+1ll*t2*(t2-1)+1ll*t1*t2*2;
cout<<ans<<endl;
return 0;
}
D - Half Reflector
翻譯
有一個神仙機器,你可以從它的左邊或者右邊丟一個球進去,它有兩種狀態\(A,B\)。如果處在\(A\)狀態,你從哪裏丟進來的就會從哪裏吐出去,然後立即變成\(B\)狀態。如果處在\(B\)狀態,你從哪邊丟進去就會從另外一邊吐出來,然後立即變成\(A\)狀態。現在把\(n\)臺這樣的機器放在一排,告訴你他們處在什麽狀態。然後從最左邊放\(K\)次球,每當前面那個球出去之後就放第二個。問最後的機器的狀態是啥。
題解
先想想一個球什麽時候會從左邊出來,什麽時候從右邊出來。如果它從左邊出來,只可能是第一個機器處於\(A\)狀態。我們來感性理解一下。首先加入你到達了中間某個位置\(i\),並且它在這裏被彈了回來,因為你到了\(i\),所以\(i-1\)原先一定是\(B\)狀態,所以它現在一定是\(A\)狀態,那麽你又被接著彈回去了,那麽你又會回到\(i\),而\(i\)剛剛是\(A\),所以現在是\(B\),所以會走到\(i+1\),然後重復此個過程,它一定會向右走。
考慮一下這個串怎麽變化。如果最高位是\(A\),直接變成\(B\)完事。否則的話,我們發現首先把它們全部取反,然後原來\(A\)之前的那個\(B\)沒有變反,原來\(B\)前面的那個\(A\)也沒有變反。所以這個操作可以看成先把它左移一位(高位截掉),再全部取反,最後一個位置顯然是補上一個\(A\)。發現做完最多\(2n\)次之後就和原來的串無關了,就變成了\(ABABAB...\)或者\(BABABA...\)這樣子了。那就暴力做個\(2n\)次,剩下的判斷一下就好啦,當然這裏情況可能有些復雜,仔細手玩一下再寫代碼。
蛤?怎麽模擬?你把數組在後面延長不就好了嗎?
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 200200
int n,K,l,r,p;
char S[MAX];int a[MAX<<2];
int main()
{
scanf("%d%d",&n,&K);scanf("%s",S+1);
l=1,r=n,p=0;
for(int i=1;i<=n;++i)a[i]=S[i]=='A';
for(int i=1;i<=n+n;++i)
{
if(a[l]^p)a[l]^=1;
else ++l,p^=1,a[++r]=p^1;
if(i==K)
{
for(int j=l;j<=r;++j)putchar((a[j]^p^1)+'A');
puts("");return 0;
}
}
if((n&1)&&(K&1))a[l]^=1;
for(int i=l;i<=r;++i)putchar((a[i]^p^1)+'A');
return 0;
}
E - Increasing Numbers
翻譯
給定一個數\(n\),\(n\le 10^{500,000}\),問\(n\)最少可以拆分成幾個不降數的和。一個不降數是在十進制位下,從高位往低位看,每個數都不會比高位的數更小的數。
題解
找啊找啊找性質。然而我太菜了,找不出性質。
首先意識不降的數一定能夠拆分成若幹個\(11....11\)的形式,因為數字不降,所以這個個數不會超過\(9\)個。那麽按照這個方向考慮,假設最終的答案中我們的所有數字被我們拆成了若幹個\(1...1\)的形式,他們分別有\(a_i\)個\(1\)。那麽等式算一下就是\(\sum_{i=1}^{9k} (10^{a_{i-1}}-1)=9n\)。轉化一下就是\(\sum_{i=1}^{9k} 10^{a_i}=9n+9k\),也就是\(9n+9k\)的所有位置上的數字之後恰好小於等於\({9k}\)。那就直接大力枚舉\(k\)就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 500500
char ch[MAX];
int a[MAX<<1],n,s;
int main()
{
scanf("%s",ch+1);n=strlen(ch+1);
for(int i=n;i;--i)a[i]=(ch[n-i+1]-48)*9;
for(int i=1;i<=n;++i)a[i+1]+=a[i]/10,a[i]%=10;
if(a[n+1])++n;
for(int i=1;i<=n;++i)s+=a[i];
for(int i=1;i<=n*9;++i)
{
a[1]+=9;int p=1;s+=9;
while(a[p]>=10)
{
s-=a[p+1];a[p+1]+=a[p]/10;s+=a[p+1];
s-=a[p];a[p]%=10;s+=a[p];
++p;if(p>n)n=p;
}
if(s<=i*9){printf("%d\n",i);return 0;}
}
return 0;
}
F - Train Service Planning
翻譯
有\(n\)段鐵路,\(n+1\)個車站,第\(i\)段鐵路連接著站臺\(i-1\)和站臺\(i\)。第\(i\)段鐵路行駛時間為\(A_i\),鐵路有兩種,有一條路或者兩條路,即一次可以開過一輛火車或者兩輛火車。現在從\(0\)到\(n\)和從\(n\)到\(0\)每經過\(K\)個單位時間就會發出一輛車,一輛車可以在一個站臺停任意久的時間,顯然不能被後面的同向的車給追上,現在需要安排一個車的行駛方式,使得兩側運行的車的總運行時間最小。
題解
首先每隔\(K\)個單位時間就會發出一輛新車,意味著所有的運算可以等效的理解為在模\(K\)意義下進行。我們假設我們知道兩輛車在每一個位置停下的時間,從\(0\rightarrow n\)的設為\(t1[i]\),反過來的設為\(t2[i]\),那麽我們就可以得到一輛車通過一段路的時間的區間。設\(S\)為前綴和。從\(0\rightarrow n\)的車的通過第\(i\)段路的時間是\([S(A,i-1)+S(t1,i-1),S(A,i)+S(t1,i-1)]\),反過來運行的車的通過時間同理,我們可以用後綴和表示出來,然後後綴又可以改寫成總和減去一段前綴,也就是\([Sum-S(A,i)-S(t2,i-1),Sum-S(A,i-1)-S(t2,i-1)]\)。而總和的話,我們可以強制認為後面從後往前的車在\(0\)車站還要多停一會兒,也就是強制讓\(sum\)是\(K\)的倍數,那麽在模\(K\)的意義下就可以直接把\(Sum\)省略掉。
對於雙向軌道而言,我們並沒有任何限制,對於單向軌道而言,顯然強制兩車通過同一段路的時間不能交。那麽可以得到一系列的不等式。最終得到的也就是形如\(S(t1,i)+S(t2,i)\notin [-2S(A,i),-2S(A,i-1)]\)。當然,這些東西全部都在模意義下看。也因為在模意義下,所以不屬於可以取補化為屬於,即\(S(t1,i)+S(t2,i)\)要在某個特定區間內。而題目要求的東西就是要最小化\(S(t1,n)+S(t2,n)\)。而問題也同時變為,給定\(n\)個區間,每次都要向前走若幹距離,並且第\(i\)步走完後位置必須在模意義下的給定區間中,求最小的移動距離。
因為可以在起始車站停留,所以我們可以任意選擇起始位置,貪心的考慮,顯然每次走到目標區間的左端點一定是最優的。那麽我們只需要預處理從每一個位置出發一直走左端點的最小距離即可。設\(f[i]\)表示當前從第\(i\)個限制的左端點出發的距離。考慮倒推,每次更新完一個區間之後就可以用線段樹更新取值,這樣子就很容易知道下一個位置要從哪裏轉移。接下來只需要枚舉起點即可計算答案。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200200
#define lson (now<<1)
#define rson (now<<1|1)
void WA(){puts("-1");exit(0);}
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,K,top;
ll a[MAX],b[MAX],t[MAX<<2];
ll L[MAX],R[MAX],f[MAX],s[MAX],S[MAX];
void pushdown(int now)
{
if(!t[now])return;
t[lson]=t[rson]=t[now];
t[now]=0;
}
void modify(int now,int l,int r,int L,int R,int w)
{
if(L>R)return;
if(L<=l&&r<=R){t[now]=w;return;}
int mid=(l+r)>>1;pushdown(now);
if(L<=mid)modify(lson,l,mid,L,R,w);
if(R>mid)modify(rson,mid+1,r,L,R,w);
}
int Query(int now,int l,int r,int p)
{
if(l==r)return t[now];
int mid=(l+r)>>1;pushdown(now);
if(p<=mid)return Query(lson,l,mid,p);
else return Query(rson,mid+1,r,p);
}
ll Query(int x)
{
int d=Query(1,1,top,x);
if(!d)return 0;
return f[d]+(S[L[d]]%K-S[x]%K+K)%K;
}
int main()
{
n=read();K=read();
for(int i=1;i<=n;++i)
{
a[i]=read(),b[i]=read();
s[i]=s[i-1]+a[i];
if(b[i]==1&&a[i]+a[i]>K)WA();
}
for(int i=n;i;--i)
if(b[i]==1)L[i]=(-2*s[i-1]%K+K)%K,R[i]=(-2*s[i]%K+K)%K;
else L[i]=0,R[i]=K-1;//No limit
for(int i=1;i<=n;++i)S[++top]=L[i],S[++top]=R[i];
sort(&S[1],&S[top+1]);top=unique(&S[1],&S[top+1])-S-1;
for(int i=n;i;--i)
{
L[i]=lower_bound(&S[1],&S[top+1],L[i])-S;
R[i]=lower_bound(&S[1],&S[top+1],R[i])-S;
f[i]=Query(L[i]);
if(L[i]>R[i])modify(1,1,top,R[i]+1,L[i]-1,i);
else modify(1,1,top,1,L[i]-1,i),modify(1,1,top,R[i]+1,top,i);
}
ll ans=1e18;
for(int i=top;i;--i)ans=min(ans,Query(i));
cout<<ans+s[n]+s[n]<<endl;
return 0;
}
AtCoder Grand Contest 011