AtCoder Grand Contest 007
AtCoder Grand Contest 007
A - Shik and Stone
翻譯
見洛谷
題解
傻逼玩意
#include<cstdio> int n,m,tot;char ch[10]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { scanf("%s",ch+1); for(int j=1;j<=m;++j) tot+=ch[j]=='#'; } puts(tot==n+m-1?"Possible":"Impossible"); return 0; }
B - Construct Sequences
翻譯
見洛谷
題解
誒,簡單構造題我也不會做,真的是對於構造一竅不通。
我們讓\(a\),\(b\)是兩個等差數列,保證\(a_i+b_i\)相等,然後公差比\(n\)大就好,每次讀進來一個\(p_i\),你就讓對應的\(a\)減去一個\(n-i\)就好了。
#include<iostream> #include<cstdio> using namespace std; #define MAX 20200 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,a[MAX],b[MAX]; int main() { n=read(); for(int i=1;i<=n;++i)a[i]=i*(n+1),b[i]=(n-i+1)*(n+1); for(int i=1;i<=n;++i)a[read()]-=(n-i+1); for(int i=1;i<=n;++i)printf("%d ",a[i]);puts(""); for(int i=1;i<=n;++i)printf("%d ",b[i]);puts(""); }
C - Pushing Balls
翻譯
數軸上有\(n\)個球和\(n+1\)個洞,每個球都在兩個洞的中間,假設把所有東西放在一起,假設相鄰兩個物品的距離為\(d_i\),那麽\(d_i-d_{i-1}=x\)。每次會等概率選擇一個球,並且等概率選擇它向左還是向右,它會一直朝著那個方向走,直到掉進坑裏,如果這個洞裏已經有球,它會從這個洞上面直接過去,繼續移動。求所有合法方案中球移動的距離和的期望。一個合法方案是所有球都恰好進入了一個洞,並且只有一個洞沒有球。
題解
神仙題,不會。DZYO的題解
官方題解:
在移動完一個球之後,重編號所有的洞和球,每個球仍然在兩個洞之間。再重新計算期望意義下相鄰的球和洞之間的距離,發現期望距離仍然是一個等差數列,然後從一號球開始順次計算答案。
代碼是照著打的。
#include<iostream>
#include<cstdio>
using namespace std;
double d,x,n,ans;
int main()
{
cin>>n>>d>>x;
for(int i=n;i;--i)
{
double sum=(d*2*n+n*(n+n-1)*x);
ans+=sum/2/n;
double dd=(d*(n+n-2)+d+2*x+3*d+3*x)/2/n;
sum-=(4*d+4*n*x-2*x)/2/n;--n;
d=dd;x=(sum-2*n*d)/n/(n+n-1);
}
printf("%.10lf\n",ans);
return 0;
}
D - Shik and Game
翻譯
(什麽傻吊題面)
有一個數軸,初始情況下玩家在\(0\)位置,出口在\(E\)位置,數軸上還有\(n\)只熊,你只要到了它的位置,再過\(T\)個單位時間它所在的位置就會出現一個金幣。求從出發到撿完所有金幣再到出口的最短時間。
題解
顯然是把熊按照維護分成若幹段,先訪問過這一段的所有熊,再回到第一個熊,再順次拿走所有金幣。考慮一個暴力\(dp\),設\(f[i]\)表示當前到了\(i\)並且前面的金幣都拿完的最短時間,然後得到式子\(f[i]=min(f[j]+max(t,2*(x[i]-x[j+1])))\),轉移很顯然。
發現走一段回去再走過來訪問到每只熊的時間恰好是這段路程的兩倍。意味著維護一下當且可以轉移過來的位置,使得距離大於\(t\),那麽它一定是一段前綴,那麽記一下前綴最小值就完事了。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100100
#define ll long long
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,E,T,x[MAX];
ll ans,f[MAX],mn=1e18;
int main()
{
n=read();E=read();T=read();
for(int i=1;i<=n;++i)x[i]=read();
memset(f,63,sizeof(f));f[0]=0;
for(int i=1,j=0;i<=n;++i)
{
for(;T<=(x[i]-x[j+1])<<1;++j)mn=min(mn,f[j]-2*x[j+1]);
if(j<i)f[i]=min(f[i],f[j]+T);
f[i]=min(f[i],mn+2*x[i]);
}
cout<<f[n]+E<<endl;
return 0;
}
E - Shik and Travel
翻譯
給定一棵二叉樹,每個節點的兒子數要麽是\(2\)要麽是\(0\)(除葉子節點外的所有點的度數都是\(2\))。現在你要訪問所有葉子節點。要求從根節點出發,最後再回到根。每天你可以選擇一個沒有去過的葉子節點,然後走過去,花費就是路徑上的權值之和。每條邊都必須恰好被走過兩次,總花費就是除了第一天從根節點出發以及最後一天回到根節點之外的每一天的花費的最大值。最小化這個最大值。
題解
最小化最大值,顯然二分。現在考慮如何判定二分值是否合法。
因為每條邊必須經過恰好兩次,所以一旦進入了一棵子樹,必定要走完所有其中所有葉子才能出去。
定義二元組\((a,b)\)表示進入到\(u\)的子樹中的時候在子樹內產生的花費是\(a\),離開子樹那天,在子樹內產生的貢獻是\(b\)。合並答案的時候枚舉兩個兒子的所有二元組,不妨設左子樹\(i\)是\((a,b)\),右子樹\(j\)是\((c,d)\),首先會合並出一條新路徑\(b+c+V_i+V_j\),判定是否合法(與二分值比較),如果合法的話,意味著我們可以合並一條路徑\((a+V_i,d+V_j)\),調換左右兒子的順序可以類似得到一個二元組\((c+V_j,b+V_i)\)。發現這樣子每次向上維護這個集合內的元素即可維護出答案。然而這樣子的狀態增長太快,我們需要減少狀態。比如說我們匹配出來的結果是\((a+V_i,d+V_j)\),那麽我們並不在意\(b,c\)是什麽,只需要他們合法就行了。那麽對於每一個\(a\),我們能夠匹配的\(d\)一定是越小越好。反過來相同。我們設節點\(u\)的二元組集合為\(S_u\),發現\(|S_u|\le 2*min(|S_i|,|S_j|)\)。我們可以選擇元素個數較小的那一邊,用它的第一維匹配元素較多的那一邊的第二維。這樣子總的狀態數就被壓成了\(O(nlgon)\)級別的,再加上二分的答案復雜度就是\(O(nlog(n)log(ans))\)。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MAX 132000
#define ll long long
#define pi pair<ll,ll>
#define pb push_back
#define mp make_pair
#define fr first
#define sd second
#define vp vector<pi>
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,w;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int n;ll mid,mn[MAX];
vp f[MAX];
bool cmpx(pi a,pi b){return a.fr<b.fr;}
bool cmpy(pi a,pi b){return a.sd<b.sd;}
void Merge(vp &u,vp ls,vp rs)
{
if(ls.size()>rs.size())swap(ls,rs);
int t1=ls.size(),t2=rs.size();
if(!t1||!t2)return;
sort(ls.begin(),ls.end(),cmpy);
sort(rs.begin(),rs.end(),cmpx);
mn[0]=rs[0].sd;for(int i=1;i<t2;++i)mn[i]=min(mn[i-1],rs[i].sd);
for(int i=0,j=t2-1;i<t1;++i)
{
while(j>=0&&ls[i].sd+rs[j].fr>mid)--j;
if(j>=0)u.pb(mp(ls[i].fr,mn[j]));
}
sort(ls.begin(),ls.end(),cmpx);
sort(rs.begin(),rs.end(),cmpy);
mn[0]=rs[0].fr;for(int i=1;i<t2;++i)mn[i]=min(mn[i-1],rs[i].fr);
for(int i=0,j=t2-1;i<t1;++i)
{
while(j>=0&&ls[i].fr+rs[j].sd>mid)--j;
if(j>=0)u.pb(mp(mn[j],ls[i].sd));
}
}
void dfs(int u)
{
f[u].clear();if(!h[u]){f[u].pb(mp(0,0));return;}
for(int i=h[u];i;i=e[i].next)dfs(e[i].v);
for(int i=h[u];i;i=e[i].next)
for(int j=0,l=f[e[i].v].size();j<l;++j)
f[e[i].v][j].fr+=e[i].w,f[e[i].v][j].sd+=e[i].w;
if(h[u])Merge(f[u],f[e[h[u]].v],f[e[e[h[u]].next].v]);
}
int main()
{
n=read();
for(int i=2,a,v;i<=n;++i)a=read(),v=read(),Add(a,i,v);
ll l=0,r=1e9,ret=1e9;
while(l<=r)
{
mid=(l+r)>>1;dfs(1);
if(f[1].empty())l=mid+1;
else ret=mid,r=mid-1;
}
cout<<ret<<endl;
return 0;
}
F - Shik and Copying String
翻譯
洛谷
題解
大概是,你畫個圖,發現只需要這樣子折一下就好了。那麽答案就是最多的折的次數。說不清啊。看一下官方題解的圖就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define MAX 1000010
int n,ans;
char s[MAX],t[MAX];
queue<int> Q;
int main()
{
scanf("%d",&n);scanf("%s",s+1);scanf("%s",t+1);
if(!strcmp(s+1,t+1)){puts("0");return 0;}
for(int i=n,p=n;i;--i)
{
if(t[i]==t[i-1])continue;
p=min(p,i);while(p&&s[p]!=t[i])--p;
if(!p){puts("-1");return 0;}
while(!Q.empty())
if((int)Q.front()-(int)Q.size()>=i)Q.pop();
else break;
Q.push(p);if(i^p)ans=max(ans,(int)Q.size());
}
printf("%d\n",ans+1);return 0;
}
AtCoder Grand Contest 007