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]) );
}
}