AtCoder Grand Contest 013
AtCoder Grand Contest 013
A - Sorted Arrays
翻译
给定一个长度为\(n\)的数组\(A\),你需要把它分割成若干段连续的区间,每一段都必须单增或者单减,求最少分多少段。
\(n\le 10^5,A_i\le 10^9\)
题解
设\(f[i]\)表示划分前\(i\)个数字的最小段数,转移就是\(f[j]+1\),满足\([j+1,i]\) 是单调的。不难发现\(f[i]\le f[i+1]\),所以直接贪心划分,每次找最长一段就可以。
#include<iostream>
#include<cstdio>
using namespace std;
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 ans,n,a[100100];
int Calc(int x)
{
if(x==n)return x;
int c=a[x]<=a[x+1];x+=1;
while(x<=n&&c==(a[x]<=a[x+1]))++x;
return x;
}
int main()
{
n=read();if(n==1){puts("1");return 0;}
for(int i=1;i<=n;++i)a[i]=read();
int tmp=1;for(int i=2;i<=n;++i)if(a[i]!=a[i-1])a[++tmp]=a[i];n=tmp;
for(int i=1;i<=n;i=Calc(i)+1)++ans;
printf("%d\n",ans);
return 0;
}
B - Hamiltonish Path
翻译
给定一张\(n\)个点\(m\)条边的连通的简单无向图。你现在要找到一条路径,满足:这条路径至少经过两个点,并且不会有点被经过两次及以上,且所有与起点或者终点相连的边都必须在路径上。
\(n,m\le 10^5\)
题解
考虑随便找一个点出来,然后向两个方向扩展,知道不能扩展为止。这样子显然是对的,因为当前点不能拓展了,证明其相邻的点都被访问过了。
但是找一个点不好搞,就随便找一条边出来对于两个点分别计算就行了。
#include<iostream>
#include<cstdio>
#include<vector>
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;
}
vector<int> E[MAX],ans1,ans2;
bool vis[MAX];
int n,m,S,T;
void dfs(int u,vector<int> &A)
{
vis[u]=true;A.push_back(u);
for(int v:E[u])if(!vis[v]){dfs(v,A);break;}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;++i)
{
S=read(),T=read();
E[S].push_back(T);
E[T].push_back(S);
}
vis[S]=vis[T]=true;
dfs(S,ans1);dfs(T,ans2);
printf("%d\n",ans1.size()+ans2.size());
while(!ans1.empty())printf("%d ",ans1.back()),ans1.pop_back();
for(int x:ans2)printf("%d ",x);puts("");
return 0;
}
C - Ants on a Circle
翻译
在一个长度为\(L\)的环上有\(n\)只蚂蚁,每只的速度都是一单位长度每秒,现在给定你这些蚂蚁的位置和移动方向,如果两只蚂蚁相碰他们就会同时朝着反方向移动。回答最后所有蚂蚁所在的位置。
题解
首先每只蚂蚁都不考虑转向的问题就可以算出最终所有蚂蚁所在的位置。
发现这个走法会使得蚂蚁的相对位置不变,那么我们只需要模拟出一号蚂蚁最终的位置,然后其他的蚂蚁顺次对应就可以求出答案了。
于是考虑怎么模拟出第一只蚂蚁最终的排名。发现如果有一只蚂蚁顺时针穿过了\(n-1\)的位置,那么第一只蚂蚁的排名会增加一,如果有一只蚂蚁逆时针穿过,那么会减少一。这样子算一算就做完了。
#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,L,T,t;
int pos[MAX];
int main()
{
n=read();L=read();T=read();
for(int i=0;i<n;++i)
{
int x=read(),w=(read()==2)?-1:1;
pos[i]=((x+w*T)%L+L)%L;
if(~w)t=(t+(x+T)/L)%n;
else t=(t+(x-T)/L-(((x-T)%L<0)?1:0)+n)%n;
}
sort(&pos[0],&pos[n]);
for(int i=t;i<n;++i)printf("%d\n",pos[i]);
for(int i=0;i<t;++i)printf("%d\n",pos[i]);
return 0;
}
D - Piling Up
E - Placing Squares
翻译
给定\(n\),你要把一个\(1*n\)的长条用若干个边长为整数的正方形的底边来覆盖,要求正方形的分割点不能在某给定的\(m\)个位置,求所有合法的分割方案的所有正方形的面积的乘积的和。
\(n\le 10^9,m\le 10^5\)
题解
考虑一个暴力\(dp\),设\(f[i]\) 表示分割前\(i\)个位置的答案,转移就是\(f[i]=\sum f[j]*(i-j)^2\) 。时间复杂度\(O(n^2)\)。
直接大力拆开,发现\(f[i]=\sum f[j]*(i^2+j^2-2ij)\),那么分别维护\(\sum f[j],\sum f[j]j,\sum f[j]j^2\)就可以做到线性复杂度。
然而这样子并不能够继续优化下去了。
我们想想办法,做一个转化,把这个乘积变为一个计数,使得这个分割方法恰好会被算贡献次。
现在要在\(n\)个格子之间放隔板,其中第一个格子之前和最后一个格子之后都强制放好了隔板。任意两个隔板之间都要放两个不同的球,求方案数。
不难发现这个问题和原问题的答案是一样的。
考虑这个问题怎么解决,设\(f[i][0/1/2]\)表示当前考虑到了\(i\)位置,当前位置和上个位置之间已经放了\(0/1/2\)个球。
转移是考虑这个位置放不放隔板,以及这个位置放不放球,这里就不展开了。
那么接下来这个\(dp\)可以直接使用矩阵乘法进行优化,时间复杂度\(O(m*3^3*logn)\)。
代码的注释部分是直接\(dp\)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MOD 1000000007
#define MAX 100100
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
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,m,a[MAX];
struct Matrix
{
int s[3][3];
void clear(){memset(s,0,sizeof(s));}
void init(){clear();for(int i=0;i<3;++i)s[i][i]=1;}
int*operator[](int x){return s[x];}
}P[31],A,B,G;
Matrix operator*(Matrix a,Matrix b)
{
Matrix c;c.clear();
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
for(int k=0;k<3;++k)
add(c[i][j],1ll*a[i][k]*b[k][j]%MOD);
return c;
}
Matrix fpow(int b)
{
Matrix s;s.init();
for(int i=0;b;b>>=1,++i)
if(b&1)s=s*P[i];
return s;
}
int f[MAX][3];
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)a[i]=read()+1;
/*
f[0][0]=1;
for(int i=1,pos=1;i<=n;++i)
{
if(i!=a[pos])
for(int j=0;j<3;++j)
add(f[i][j],((j==1)?2:1)*f[i-1][2]%MOD);
for(int j=0;j<3;++j)
{
add(f[i][j],f[i-1][j]);
if(j<2)add(f[i][j+1],(2-j)*f[i-1][j]%MOD);
if(j<1)add(f[i][j+2],f[i-1][j]);
}
if(i==a[pos])++pos;
}
printf("%d\n",f[n][2]);
*/
A[0][0]=1;A[0][1]=2;A[0][2]=1;
A[1][0]=0;A[1][1]=1;A[1][2]=1;
A[2][0]=1;A[2][1]=2;A[2][2]=2;
B=A;
B[2][0]=0;B[2][1]=0;B[2][2]=1;
P[0]=A;for(int i=1;i<=30;++i)P[i]=P[i-1]*P[i-1];
G[0][0]=1;
for(int i=1;i<=m;++i)G=G*fpow(a[i]-a[i-1]-1)*B;
G=G*fpow(n-a[m]);
printf("%d\n",G[0][2]);
return 0;
}
F - Two Faced Cards
翻译
有两副牌,第一副有\(n\)张卡,第\(i\)张正面写着\(A_i\),背面写着\(B_i\),我们称这副牌为\(X\)。
第二副牌有\(n+1\)张卡,只写了\(C_i\),我们称之为\(Y\)。
有\(Q\)次询问,每次询问独立。每次会往\(X\)中加入一张\(D_i,E_i\)的牌,加完之后称之为\(Z\)。现在你要把\(Z\)和\(Y\)中的牌两两配对,你可以指定\(Z\)中的牌用正面还是反面,最后需要保证任意一对牌中,\(Z\)用的那一面的数字要小于等于\(Y\)上的数字。定义一个匹配的权值为\(Z\)中正面朝上的牌的个数,对于每次询问,回答最大权值。如果没有可行的方案输出\(-1\)。
\(n\le 10^5\)
题解
不太会,咕了咕了,以后补。