Codeforces Round #545 Div1 題解
Codeforces Round #545 Div1 題解
A-Skyscrapers
題意
給定一個 \(n∗m\) 的網格,每個格子裡都有一個數,對於任意一行和任意一列,要求把這 \(n+m−1\) 個數重新用正整數編號,並且對於這一行,數與數之間的大小關係不變,對於這一列同理。求出任意一行和任意一列編號使用的最大編號的最小值。
題解
首先對於每一行每一列單獨離散化,記錄每個位置在離散化之後的值,合併之後取較大的,剩下部分順次編號即可。
Code
#include <bits/stdc++.h> using namespace std; const int N=1010; int n,m,r[N],l[N],a[N][N],b[N][N],c[N][N]; int s[N],top; int main() { scanf( "%d%d",&n,&m ); for ( int i=1; i<=n; i++ ) for ( int j = 1; j<=m; j++ ) scanf( "%d",&a[i][j] ); for ( int i=1; i<=n; i++ ) { top=0; for ( int j=1; j<=m; j++ ) s[++top]=a[i][j]; sort( s+1,s+1+top ); top=unique(s+1,s+1+top)-s-1; for ( int j=1; j<=m; j++ ) b[i][j]=lower_bound( s+1,s+1+top,a[i][j] )-s; r[i]=top; } for ( int i=1; i<=m; i++ ) { top=0; for ( int j=1; j<=n; j++ ) s[++top]=a[j][i]; sort( s+1,s+1+top ); top=unique(s+1,s+1+top)-s-1; for ( int j=1; j<=n; j++ ) c[j][i]=lower_bound( s+1,s+1+top,a[j][i] )-s; l[i]=top; } for ( int i=1; i<=n; i++,printf("\n") ) for ( int j=1; j<=m; j++ ) { int ans1=max( r[i],max( l[j],b[i][j]+l[j]-c[i][j] ) ); int ans2=max( r[i],max( l[j],c[i][j]+r[i]-b[i][j] ) ); printf( "%d ",max( ans1,ans2 ) ); } }
B-Camp Schedule
題意
給定兩個 \(01\) 串 \(S\) 和 \(T\) ,把 \(S\) 打亂,使得 \(T\) 在其中出現次數最多。
題解
對於 \(T\) 跑 \(KMP\) ,然後先建一次 \(T\) ,每次跳到 \(next\) 接著往後放即可
Code
#include <bits/stdc++.h> using namespace std; const int N=5e5+10; char ch[N]; int n,m,nxt[N]; int main() { scanf( "%s",ch+1 ); for ( int i=1,l=strlen(ch+1); i<=l; i++ ) if ( ch[i]=='0' ) n++; else m++; scanf( "%s",ch+1 ); int len=strlen(ch+1); nxt[1]=0; for ( int i=2; i<=len; i++ ) { int tmp=nxt[i-1]; while ( tmp && ch[tmp+1]!=ch[i] ) tmp=nxt[tmp]; if ( !tmp ) { if ( ch[1]==ch[i] ) nxt[i]=1; else nxt[i]=0; } else nxt[i]=tmp+1; } bool flag=1; for ( int i=1; i<=len; i++ ) if ( ch[i]=='0' ) { if ( !n ) { flag=0; break; } n--; printf( "0" ); } else { if ( !m ) { flag=0; break; } m--; printf( "1" ); } while ( (n || m ) && flag ) { for ( int i=nxt[len]+1; i<=len; i++ ) if ( ch[i]=='0' ) { if ( !n ) { flag=0; break; } n--; printf( "0" ); } else { if ( !m ) { flag=0; break; } m--; printf( "1" ); } } while ( n-- ) printf( "0" ); while ( m-- ) printf( "1" ); }
C-Museums Tour
題意
給定一張 \(n\) 點的有向圖,每個點有博物館並給定每週開放狀態,一週的長度為 \(d\) 天,在某一週的第一天從 \(1\) 出發,每條邊邊權為 \(1\) ,途中不能逗留,問最多可以訪問幾個博物館。
題解
首先根據時間拆點, \((i,j)\) 就是當前在城市 \(i\) ,星期 \(j\) ,對於原圖 \(u\to v\),連邊 \((u,i)\to (v,i \mod d+1)\)
對這個圖求 \(SCC\) ,統計每個裡面有多少不同的博物館。然後找一條從 \(1\) 開始的最長鏈即可。
Code
#include <bits/stdc++.h> using namespace std; const int N=1e5+10; int head[N],head2[N]; struct edge { int nxt,ver,val; }e1[N<<1],e2[N*2*50]; int tot,tot2,cnt,n,m,d,gcd,g[N],f[N][55],pre[N][55],deg[N],val[N],c[N]; int dfn[N],low[N],num,top,sta[N]; char v[N][55]; bool vis[N],ins[N]; vector<int> scc[N]; void add( int x,int y ) { tot++; e1[tot]=(edge){ head[x],y,0 }; head[x]=tot; } void add2( int x,int y,int z ) { tot2++; e2[tot2]=(edge){ head2[x],y,z }; head2[x]=tot2; deg[y]++; } void solve( int x,int p ) { val[x]=p; vis[x]=1; for ( int i=head[x]; i; i=e1[i].nxt ) { int y=e1[i].ver; if ( c[y]!=cnt ) continue; if ( !vis[y] ) solve(y,p+1); else gcd=__gcd( gcd,abs(p+1-val[y]) ); } } void tarjan( int x ) { dfn[x]=low[x]=++num; sta[++top]=x; ins[x]=1; for ( int i=head[x]; i; i=e1[i].nxt ) if ( !dfn[e1[i].ver] ) tarjan( e1[i].ver ),low[x]=min( low[x],low[e1[i].ver] ); else if ( ins[e1[i].ver] ) low[x]=min( low[x],dfn[e1[i].ver] ); if ( dfn[x]==low[x] ) { cnt++; int y; do { y=sta[top--]; ins[y]=0; c[y]=cnt; scc[cnt].push_back(y); }while ( x!=y ); gcd=d; solve( x,0 ); g[cnt]=gcd; for ( int i=0; i<gcd; i++ ) for ( int j=0; j<scc[cnt].size(); j++ ) for ( int k=i; k<d; k+=gcd ) if ( v[scc[cnt][j]][k]=='1' ) { v[scc[cnt][j]][i]='1'; break; } for ( int i=0; i<gcd; i++ ) for ( int j=0; j<scc[cnt].size(); j++ ) if ( v[scc[cnt][j]][(val[scc[cnt][j]]+i)%gcd]=='1' ) pre[cnt][i]++; } } void dp() { int ans=0; memset( f,-0x3f,sizeof(f) ); f[c[1]][0]=pre[c[1]][0]; queue<int> q; for ( int i=1; i<=cnt; i++ ) if ( !deg[i] ) q.push(i); while ( !q.empty() ) { int x=q.front(); q.pop(); for ( int i=head2[x]; i; i=e2[i].nxt ) { int y=e2[i].ver; deg[y]--; if ( deg[y]==0 ) q.push(y); } for ( int i=head2[x]; i; i=e2[i].nxt ) for ( int j=0; j<d; j++ ) { int y=e2[i].ver,z=e2[i].val; f[y][(z+j)%d]=max( f[y][(z+j)%d],f[x][j]+pre[y][(z+j)%g[y]] ); } for ( int i=0; i<d; i++ ) ans=max( ans,f[x][i] ); } printf( "%d",ans ); } int main() { scanf( "%d%d%d",&n,&m,&d ); for ( int i=1,u,v; i<=m; i++ ) scanf( "%d%d",&u,&v ),add( u,v ); for ( int i=1; i<=n; i++ ) scanf( "%s",v[i] ); for ( int i=1; i<=n; i++ ) if ( !dfn[i] ) tarjan( i ); for ( int j=1; j<=n; j++ ) for ( int i=head[j]; i; i=e1[i].nxt ) { int y=e1[i].ver; if ( c[j]==c[y] ) continue; int d2=__gcd(g[c[j]],g[c[y]] ); int d1=(val[j]-val[y]+1+d)%d; for ( int k=d1%d2; k<d; k+=d2 ) add2( c[j],c[y],k ); } dp(); }
D-Cooperative Game (互動題)
題意
有一張圖是由一個長度為 \(t\) 的鏈和一個大小為 \(c\) 的環中間連上一條邊組成的。假如這條邊連線的是鏈的右端點,和環上的 \(T\) 點。令鏈的左端點是 \(S\) 。
現在在 \(S\) 處有 \(10\) 個棋子,編號 \(0−9\),每次你可以讓任意數量的棋子向出邊方向走一步,互動庫會返回若干個集合,每一個集合內的棋子都在同一個位置上,並且這個位置上的所有棋子都在這個集合中。
現在你既不知道 \(t\) 也不知道 \(c\) 。你需要使用不超過 \(3(t+c)\) 次操作使得所有棋子都移動到T位置上並且返回互動庫done
。
題解
(第一次做互動題啊……)
首先讓兩個棋子移動,一個每次操作都向前走,另外一個每兩次操作才向前走。當兩個棋子相遇時停止。
把環按照 \(T\) 點開始沿出邊方向標號。
設當第二個棋子位於 \(T\) ,即 \(0\) 位置時,假設第一個棋子在 \(p\) 位置。
因為第一個棋子每次比第二個棋子多走一步,距離差是 \(c−p\)。
所以接下來第二個棋子要移動的步數就是 \(c−p\)。所以相遇時兩個棋子在 \(c−p\) 位置。
因為第二個棋子到達 \(T\) 時走了 \(t\) 步,此時第一個棋子走了 \(2t\) 步,即他在環上走了 \(t\) 步,所以有 \(t\mod c=p\),那麼讓 \(10\) 個棋子同時向前走,當所有棋子位於同一個點時他們就同時到達了 \(T\) 。
即這裡還需要走 \(t\) 步,而這 \(t\) 步等價於在環上走了 \(p\) 步,那麼 \(1,2\) 兩個棋子就從 \(c−p\) 走到了 \(0\) 位置。
注意輸出要刷快取,可以用 cout
或者是 fflush(stdout)
.
Code
#include <bits/stdc++.h>
using namespace std;
char ch[20];
int read()
{
int x; scanf( "%d",&x );
for ( int i=1; i<=x; i++ )
scanf( "%s",ch );
return x;
}
int main()
{
while ( 1 )
{
cout<<"next 0"<<endl; read();
cout<<"next 0 1"<<endl;
int tot=read();
if ( tot==2 ) break;
}
while ( 1 )
{
cout<<"next 0 1 2 3 4 5 6 7 8 9"<<endl;
if ( read()==1 ) break;
}
cout<<"done";
}
E-Train Car Selection
題意
你有一列有 \(n\) 個車廂的火車,從車頭開始 \(1−n\) 編號。
現在有 \(3\) 種操作,第一種是在車頭位置加入 \(k\) 節車廂,第二種位置是在車尾位置加入 \(k\) 節車廂,第三種是修改每節車廂的價值。
價值定義為:一開始所有車廂的價值都是 \(0\) (包括中途加入的車廂),然後每次修改會給定 \(s,b\),如果當前車廂是從車頭開始數的第 \(i\) 節,那麼它的價值就會加上 \((i−1)∗s+b\) 。
在每次操作結束之後回答價值最小的車廂的編號以及其價值。如果有多個輸出編號最小的那個。
題解
發現每次一起加入進來的東西只需要維護左端點就好了。
首先如果在前端插入一段,那麼後面全部都沒用了,可以直接丟掉。
否則在後面插入一段,維護一下當前全域性加的一次函式是什麼,那麼每個左端點按照車廂編號+權值可以寫成一個個的點,顯然只有一個上凸殼才有用,那麼維護這個上凸殼就行了。
Code
#include <bits/stdc++.h>
#define ll long long
#define PI pair<ll,ll>
using namespace std;
const int N=3e5+10;
PI p[N];
int n,m,r=1;
ll k=0,b=0;
double get_k( PI a,PI b )
{
return 1.0*(a.second-b.second)/(a.first-b.first);
}
ll calc( PI x )
{
return k*x.first+x.second+b;
}
int main()
{
scanf( "%d%d",&n,&m );
p[1]=make_pair( 0,0 ); r=1;
while ( m-- )
{
int opt; scanf( "%d",&opt );
if ( opt==1 )
{
r=1; p[1]=make_pair( 0,0 ); k=b=0;
int tmp; scanf( "%d",&tmp ); n+=tmp;
}
else if ( opt==2 )
{
PI now=make_pair( n,-(n*k+b) );
while ( r>1 && get_k(now,p[r])<=get_k( p[r],p[r-1] ) ) r--;
p[++r]=now; int tmp; scanf( "%d",&tmp ); n+=tmp;
}
else
{
int x,y; scanf( "%d%d",&x,&y );
b+=x; k+=y;
}
while ( r>1 && calc( p[r] )>=calc( p[r-1] ) ) r--;
printf( "%lld %lld\n",p[r].first+1,calc(p[r]) );
}
}