XIII Open Cup named after E.V. Pankratiev. GP of Ukraine

Claris發表於2016-10-08

A. Automaton

字尾自動機可以得到$O(2n+1)$個狀態,但是字尾自動機會拒絕接收所有不是$S$的子串的串,所以在建立字尾自動機的時候不復制節點即可得到$n+1$個狀態的DFA。

#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<time.h>
#include<assert.h>
#include<iostream>
using namespace std;
typedef long long LL;
typedef pair<int,int>pi;

const int Maxn=200020;
int n,q,sz,last,len;
int ml[Maxn],f[Maxn],ch[Maxn][26];
vector<int>G[Maxn];
string ss[Maxn];
string s;
void add(int c)
{
	int p=last;
	if(ch[p][c])
	{
		int q=ch[p][c];
		if(ml[q]==ml[p]+1)last=q;
		else
		{
			int nq=++sz;
			memcpy(ch[nq],ch[q],sizeof(ch[q]));
			ml[nq]=ml[p]+1;
			last=nq;
			f[nq]=f[q];
			f[q]=nq;
			for(;p&&ch[p][c]==q;p=f[p])ch[p][c]=nq;
		}
	}
	else
	{
		int np=++sz;last=np;
		memset(ch[np],0,sizeof ch[np]);
		ml[np]=ml[p]+1;
		for(;p&&!ch[p][c];p=f[p])ch[p][c]=np;
		if(!p){f[np]=1;return;}
		int q=ch[p][c];
		f[np]=q;return;//
		if(ml[q]==ml[p]+1){f[np]=q;return;}
		int nq=++sz;
		memcpy(ch[nq],ch[q],sizeof(ch[q]));
		ml[nq]=ml[p]+1;
		f[nq]=f[q];
		f[q]=f[np]=nq;
		for(;p&&ch[p][c]==q;p=f[p])ch[p][c]=nq;
	}
}
void init(){
  sz=1;memset(ch[sz],0,sizeof ch[sz]);
}
void insert(string &s)
{
	last=1;
	for(int i=0;i<s.size();i++)
	{
		add(s[i]-'a');
	}
}
struct Node{
	int u,v;
	char c;
	Node(){}
	Node(int u,int v,char c):u(u),v(v),c(c){}
};
bool check(int st){
	int cur=1;
	for(int i=st;i<s.size();i++){
		if(!ch[cur][s[i]-'a'])return 0;
		cur=ch[cur][s[i]-'a'];
	}
	return 1;
}
int main(){
	while(cin>>s){
		//s.assign(20,'a');
		//for(int i=0;i<20;i++)s[i]=rand()%5+s[i];
		memset(ch,0,sizeof ch);
		init();
		insert(s);
		vector<Node>rep;
		for(int i=1;i<=sz;i++){
			for(int j=0;j<26;j++){
				if(ch[i][j])rep.push_back(Node(i,ch[i][j],j+'a'));
			}
		}
		/*
		for(int i=0;i<s.size();i++){
			if(!check(i)){
				puts("wa");
				cout<<s<<endl;
				printf("%d\n",i);
				while(1);
			}
		}
		*/
		printf("%d %d\n",sz,(int)rep.size());
		for(int i=0;i<rep.size();i++)printf("%d %d %c\n",rep[i].u,rep[i].v,rep[i].c);
	}
	return 0;
}

  

B. Beinz

用Lucas定理計算組合數即可,時間複雜度$O(p+t\log n)$。

#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<time.h>
#include<assert.h>
#include<iostream>
using namespace std;
typedef long long LL;
typedef pair<int,int>pi;
const int mod=1e9+7;
const int Maxn=1002000;
int fac[Maxn],rev[Maxn];
int C(int x,int y){
	if(x<y||y<0)return 0;
	return 1LL*fac[x]*rev[y]%mod*rev[x-y]%mod;
}
int main(){
	int k,p,t;
	LL n;
	fac[0]=fac[1]=rev[0]=rev[1]=1;
	for(int i=2;i<Maxn;i++)fac[i]=1LL*i*fac[i-1]%mod;
	for(int i=2;i<Maxn;i++)rev[i]=1LL*(mod-mod/i)*rev[mod%i]%mod;
	for(int i=2;i<Maxn;i++)rev[i]=1LL*rev[i-1]*rev[i]%mod;
	while(scanf("%d%d%d",&k,&p,&t)!=EOF){
		while(t--){
			scanf("%lld",&n);
			LL ans=1;
			while(n){
				ans=1LL*ans*C(n%p+k-1,k-1)%mod;
				n/=p;
			}
			printf("%lld\n",ans);
		}
	}
	return 0;
}

  

C. Cutting

首先將串插入Trie中,可以去掉一些冗餘狀態。

設$dp[l][r][x][y]$表示$[l,r]$這個區間切成串$x$的字首$y$的最少步數,可以列舉下一個匹配點轉移。

設$f[l][r]$表示$[l,r]$消完的最小步數,可以暴力轉移求解。

在計算$dp$的時候加上可行性剪枝即可,複雜度達不到理論上界$O(n^5)$。

#include <bits/stdc++.h>
using namespace std ;

typedef long long LL ;
typedef pair < int , int > pii ;

#define clr( a , x ) memset ( a , x , sizeof a )

const int N = 105 ;
const int MAXN = 10005 ;
const int INF = 0x3f3f3f3f ;

int node[MAXN][26] ;
int word[MAXN] ;
char s[N] , p[N] ;
int dp[N][MAXN] ;
int jump[N][26] , pre[26] ;
int f[N][N] ;
int n , m , cur ;

int newnode () {
	clr ( node[cur] , 0 ) ;
	word[cur] = 0 ;
	return cur ++ ;
}

void insert ( char s[] ) {
	int now = 0 ;
	for ( int i = 0 ; s[i] ; ++ i ) {
		int x = s[i] - 'a' ;
		if ( !node[now][x] ) node[now][x] = newnode () ;
		now = node[now][x] ;
	}
	word[now] = 1 ;
}

void solve () {
	n = strlen ( s + 1 ) ;
	for ( int i = 0 ; i < 26 ; ++ i ) {
		pre[i] = n + 1 ;
	}
	for ( int i = n ; i >= 0 ; -- i ) {
		for ( int j = 0 ; j < 26 ; ++ j ) {
			jump[i][j] = pre[j] ;
		}
		pre[s[i] - 'a'] = i ;
	}
	cur = 0 ;
	newnode () ;
	for ( int i = 0 ; i < m ; ++ i ) {
		scanf ( "%s" , p ) ;
		insert ( p ) ;
	}
	clr ( f , INF ) ;
	for ( int l = n ; l >= 1 ; -- l ) {
		clr ( dp , INF ) ;
		dp[l - 1][0] = 0 ;
		for ( int r = l ; r <= n ; ++ r ) {
			for ( int now = 0 ; now < cur ; ++ now ) if ( dp[r - 1][now] < INF ) {
				for ( int i = 0 ; i < 26 ; ++ i ) if ( node[now][i] ) {
					int nxt = node[now][i] ;
					int x = jump[r - 1][i] ;
					while ( x <= n ) {
						int tmp = r <= x - 1 ? f[r][x - 1] : 0 ;
						dp[x][nxt] = min ( dp[x][nxt] , dp[r - 1][now] + tmp ) ;
						if ( word[nxt] ) f[l][x] = min ( f[l][x] , dp[x][nxt] + 1 ) ;
						x = jump[x][i] ;
					}
				}
			}
		}
		for ( int r = l ; r <= n ; ++ r ) {
			for ( int i = l ; i < r ; ++ i ) {
				f[l][r] = min ( f[l][r] , f[l][i] + f[i + 1][r] ) ;
			}
		}
	}
	printf ( "%d\n" , f[1][n] ) ;
}

int main () {
	while ( ~scanf ( "%s%d" , s + 1 , &m ) ) solve () ;
	return 0 ;
}

  

D. Disclosure

對於一條邊$x->y$,若去掉之後$x$不能到達$y$,那麼它是必需的。

首先拓撲排序求出拓撲序,然後按照終點拓撲序為第一關鍵字,起點拓撲序為第二關鍵字從小到大加邊。

對於每個點,維護一個bitset,表示當前從哪些點可以到達自己。

時間複雜度$O(\frac{nm}{32})$。

#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=50002,M=50002;
int n,m,i,j,x,y,d[N],g[N],g2[N],v[M],v2[M],nxt[M],nxt2[M],ed,h,t,q[N],ans;bitset<N>f[N];P b[M];
inline void add(int x,int y){d[y]++;v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
inline void add2(int x,int y){v2[++ed]=y;nxt2[ed]=g2[x];g2[x]=ed;}
int main(){
  scanf("%d%d",&n,&m);
  for(i=1;i<=n;i++)f[i][i]=1;
  while(m--)scanf("%d%d",&x,&y),add(x,y);
  for(ed=0,i=h=1;i<=n;i++)if(!d[i])q[++t]=i;
  while(h<=t)for(i=g[x=q[h++]];i;add2(v[i],x),i=nxt[i])if(!(--d[v[i]]))q[++t]=v[i];
  for(i=1;i<=n;i++)for(j=g2[x=q[i]];j;f[x]|=f[v2[j]],j=nxt2[j])if(!f[x][v2[j]])b[++ans]=P(v2[j],x);
  sort(b+1,b+ans+1);
  for(i=1;i<=ans;i++)printf("%d %d\n",b[i].first,b[i].second);
  return 0;
}

  

E. Embedded circles

如果詢問$i$的範圍完全包含詢問$j$,那麼$i$向$j$連邊,這樣可以得到一棵樹。

因為題目的限制,所以它給出的是這棵樹的dfs序列。

維護一個棧,按深度維護當前詢問到根的路徑,每次讀入一個新的詢問,如果它的圓心不被棧頂詢問包含,那麼彈棧,這樣就可以找到當前詢問的父親。

每次彈棧的時候計算被彈詢問的答案,為了保證複雜度均攤線性,需要找到若干個屬於它的且不屬於它任何孩子的一個起點,然後從這些起點開始BFS染色。

每次BFS染色出界的時候,在棧中二分查詢出深度最大的包含了這個位置的詢問,那麼這個點就是那個詢問的一個可能的起點。

時間複雜度$O(RC+Q\log Q)$。

#include<cstdio>
const int N=2010,M=1000010,E=N*N*4;
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int n,m,i,j,cq,e[M][3],q[M],t,ans[M];char a[N][N];
int que[N*N],head,tail;bool vis[N][N];
int g[M],v[E],nxt[E],ed;
long long all;
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline int sqr(int x){return x*x;}
inline bool check(int x,int y,int p){
  return sqr(x-e[p][0])+sqr(y-e[p][1])<=e[p][2];
}
inline void add(int x,int y){
  int l=1,r=t-1,mid,o=0;
  while(l<=r)if(check(x,y,q[mid=(l+r)>>1]))l=(o=mid)+1;else r=mid-1;
  if(o)v[++ed]=x<<11|y,nxt[ed]=g[q[o]],g[q[o]]=ed;
}
inline void bfs(int o,int x,int y){
  if(vis[x][y])return;
  que[head=tail=1]=x<<11|y;
  vis[x][y]=1;
  while(head<=tail){
    x=que[head++];
    y=x&2047;x>>=11;
    ans[o]+=a[x][y];
    for(int d=0;d<4;d++){
      int nx=x+dx[d],ny=y+dy[d];
      if(nx<1||nx>n||ny<1||ny>m||vis[nx][ny])continue;
      if(check(nx,ny,o)){
        que[++tail]=nx<<11|ny;
        vis[nx][ny]=1;
      }else{
        add(nx,ny);
      }
    }
  }
}
inline void pop(){
  int o=q[t];
  bfs(o,e[o][0],e[o][1]);
  for(int i=g[o];i;i=nxt[i])bfs(o,v[i]>>11,v[i]&2047);
  ans[q[--t]]+=ans[o];
}
int main(){
  read(n),read(m);
  for(i=1;i<=n;i++){
    scanf("%s",a[i]+1);
    for(j=1;j<=m;j++)a[i][j]-='0';
  }
  read(cq);
  for(i=1;i<=cq;q[++t]=i++){
    read(e[i][0]),read(e[i][1]),read(e[i][2]);e[i][2]*=e[i][2];
    while(t&&!check(e[i][0],e[i][1],q[t]))pop();
  }
  while(t)pop();
  for(i=1;i<=cq;i++)all+=ans[i];
  printf("%lld",all);
  return 0;
}

  

F. False figures

留坑。

 

G. Grouping

首先將$a$從小到大排序,那麼相當於要求把$a$分成$k$段,每段計算所有數到區間中位數的差的絕對值之和,要求最小化這個和。

因為$a$有序,所以計算一段的代價可以用字首和做到$O(1)$。

設$f[i][j]$表示前$j$個數分成$i$段的最小代價,那麼$f[i][j]=\min(f[i-1][k]+cost(k+1,j))$,$(0\leq k<j)$。

轉移具有決策單調性,所以可以分治求解。

時間複雜度$O(nk\log n)$。

#include<cstdio>
#include<algorithm>
const int N=5005,inf=2100000000;
typedef unsigned int U;
int n,m,i,o,a[N],s[N];U f[2][N];
inline U cal(int l,int r){
  int m=(l+r)>>1;
  return (m+m-l-r)*a[m]-s[m]+s[l-1]+s[r]-s[m-1];
}
void dp(int l,int r,int dl,int dr){
  int m=(l+r)>>1,dm=dl;
  U tmp=inf;
  for(int i=dl;i<=dr&&i<m;i++){
    U now=f[o^1][i]+cal(i+1,m);
    if(now<tmp)tmp=now,dm=i;
  }
  f[o][m]=tmp;
  if(l<m)dp(l,m-1,dl,dm);
  if(r>m)dp(m+1,r,dm,dr);
}
int main(){
  scanf("%d%d",&n,&m);
  for(i=1;i<=n;i++)scanf("%d",&a[i]);
  std::sort(a+1,a+n+1);
  for(i=1;i<=n;i++)s[i]=s[i-1]+a[i];
  for(i=1;i<=n;i++)f[0][i]=inf;
  while(m--){
    o^=1;
    for(i=0;i<=n;i++)f[o][i]=inf;
    dp(1,n,0,n);
  }
  printf("%u",f[o][n]);
  return 0;
}

  

H. Hidden triangles

留坑。

 

I. Interactive

用FFT加速多項式乘法即可,注意需要開long double。

時間複雜度$O(n\log n)$。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=131100;
int n,i,j,k,pos[N],a[N],b[N];long long c[N];
namespace FFT{
struct comp{
  long double r,i;comp(long double _r=0,long double _i=0){r=_r,i=_i;}
  comp operator+(const comp&x){return comp(r+x.r,i+x.i);}
  comp operator-(const comp&x){return comp(r-x.r,i-x.i);}
  comp operator*(const comp&x){return comp(r*x.r-i*x.i,r*x.i+i*x.r);}
  comp conj(){return comp(r,-i);}
}A[N],B[N];
const long double pi=acos(-1.0);
void FFT(comp a[],int n,int t){
  for(int i=1;i<n;i++)if(i<pos[i])swap(a[i],a[pos[i]]);
  for(int d=0;(1<<d)<n;d++){
    int m=1<<d,m2=m<<1;
    long double o=pi*2/m2*t;comp _w(cos(o),sin(o));
    for(int i=0;i<n;i+=m2){
      comp w(1,0);
      for(int j=0;j<m;j++){
        comp&A=a[i+j+m],&B=a[i+j],t=w*A;
        A=B-t;B=B+t;w=w*_w;
      }
    }
  }
  if(t==-1)for(int i=0;i<n;i++)a[i].r/=n;
}
void mul(int*a,int*b,long long*c){
  int i,j;
  for(i=0;i<k;i++)A[i]=comp(a[i],b[i]);
  FFT(A,k,1);
  for(i=0;i<k;i++){
    j=(k-i)&(k-1);
    B[i]=(A[i]*A[i]-(A[j]*A[j]).conj())*comp(0,-0.25);
  }
  FFT(B,k,-1);
  for(i=0;i<k;i++)c[i]=(long long)(B[i].r+0.5);
}
}
int main(){
  scanf("%d",&n);
  for(k=1;k<n;k<<=1);k<<=1;
  j=__builtin_ctz(k)-1;
  for(i=0;i<k;i++)pos[i]=pos[i>>1]>>1|((i&1)<<j);
  for(i=0;i<n;i++)scanf("%d",&a[i]);
  for(i=0;i<n;i++)scanf("%d",&b[i]);
  FFT::mul(a,b,c);
  for(i=0;i<n+n-1;i++)printf("%lld%c",c[i],i<n+n-2?' ':'\n');
  return 0;
}

  

J. Joinery

列舉長方體的$8$個端點,然後列舉$6$個面,在每個面上三分套三分查詢表面距離最遠的點即可。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
double ans,r;
const double eps=1e-8;
double LL,WW,HH;
struct P{
  double x,y,z;
  P(){}
  P(double _x,double _y,double _z){x=_x,y=_y,z=_z;}
}a[8];
void turn(int i,int j,double x,double y,double z,double x0,double y0,double L,double W,double H){
  if(z==0){
    double R=x*x+y*y;
    if(R<r)r=R;
  }else{
    if(i>=0&&i<2)turn(i+1,j,x0+L+z,y,x0+L-x,x0+L,y0,H,W,L);
    if(j>=0&&j<2)turn(i,j+1,x,y0+W+z,y0+W-y,x0,y0+W,L,H,W);
    if(i<=0&&i>-2)turn(i-1,j,x0-z,y,x-x0,x0-H,y0,H,W,L);
    if(j<=0&&j>-2)turn(i,j-1,x,y0-z,y-y0,x0,y0-H,L,H,W);
  }
}
double cal(double x1,double y1,double z1,double x2,double y2,double z2){
  double L=LL,W=WW,H=HH;
  if(fabs(z1)>eps&&fabs(z1-H)>eps)if(fabs(y1)<eps||fabs(y1-W)<eps)
    swap(y1,z1),swap(y2,z2),swap(W,H);
  else
    swap(x1,z1),swap(x2,z2),swap(L,H);
  if(fabs(z1-H)<eps)z1=0,z2=H-z2;
  r=1e20;
  turn(0,0,x2-x1,y2-y1,z2,-x1,-y1,L,W,H);
  return r;
}
double solvexy(double x,double y,double z,double A,double B,double C){
  double l=0,r=C,m1,m2,s1,s2;
  double ret=0;
  while(l+eps<r){
    double len=(r-l)/3.0;
    m1=l+len;
    m2=r-len;
    s1=cal(x,y,z,A,B,m1);
    s2=cal(x,y,z,A,B,m2);
    ret=max(ret,max(s1,s2));
    if(s1>s2)r=m2;else l=m1;
  }
  return ret;
}
double solvexz(double x,double y,double z,double A,double B,double C){
  double l=0,r=B,m1,m2,s1,s2;
  double ret=0;
  while(l+eps<r){
    double len=(r-l)/3.0;
    m1=l+len;
    m2=r-len;
    s1=cal(x,y,z,A,m1,C);
    s2=cal(x,y,z,A,m2,C);
    ret=max(ret,max(s1,s2));
    if(s1>s2)r=m2;else l=m1;
  }
  return ret;
}
double solvex(double x,double y,double z,double A,double B,double C){
  double l=0,r=B,m1,m2,s1,s2;
  double ret=0;
  while(l+eps<r){
    double len=(r-l)/3.0;
    m1=l+len;
    m2=r-len;
    s1=solvexy(x,y,z,A,m1,C);
    s2=solvexy(x,y,z,A,m2,C);
    ret=max(ret,max(s1,s2));
    if(s1>s2)r=m2;else l=m1;
  }
  return ret;
}
double solvey(double x,double y,double z,double A,double B,double C){
  double l=0,r=A,m1,m2,s1,s2;
  double ret=0;
  while(l+eps<r){
    double len=(r-l)/3.0;
    m1=l+len;
    m2=r-len;
    s1=solvexy(x,y,z,m1,B,C);
    s2=solvexy(x,y,z,m2,B,C);
    ret=max(ret,max(s1,s2));
    if(s1>s2)r=m2;else l=m1;
  }
  return ret;
}
double solvez(double x,double y,double z,double A,double B,double C){
  double l=0,r=A,m1,m2,s1,s2;
  double ret=0;
  while(l+eps<r){
    double len=(r-l)/3.0;
    m1=l+len;
    m2=r-len;
    s1=solvexz(x,y,z,m1,B,C);
    s2=solvexz(x,y,z,m2,B,C);
    ret=max(ret,max(s1,s2));
    if(s1>s2)r=m2;else l=m1;
  }
  return ret;
}
int main(){
  int i,j;
  double L,H,W;
  scanf("%lf%lf%lf",&L,&W,&H);
  a[0]=P(0,0,0);
  a[1]=P(0,0,H);
  a[2]=P(0,W,0);
  a[3]=P(0,W,H);
  a[4]=P(L,0,0);
  a[5]=P(L,0,H);
  a[6]=P(L,W,0);
  a[7]=P(L,W,H);
  LL=L;
  WW=W;
  HH=H;
  for(i=0;i<8;i++)for(j=0;j<8;j++)
    ans=max(ans,cal(a[i].x,a[i].y,a[i].z,a[j].x,a[j].y,a[j].z));
  for(i=0;i<8;i++){
    ans=max(ans,solvex(a[i].x,a[i].y,a[i].z,0,W,H));
    ans=max(ans,solvex(a[i].x,a[i].y,a[i].z,L,W,H));
    ans=max(ans,solvey(a[i].x,a[i].y,a[i].z,L,0,H));
    ans=max(ans,solvey(a[i].x,a[i].y,a[i].z,L,W,H));
    ans=max(ans,solvez(a[i].x,a[i].y,a[i].z,L,W,0));
    ans=max(ans,solvez(a[i].x,a[i].y,a[i].z,L,W,H));
  }
  double ret=sqrt(ans);
  printf("%.13f",ret);
  return 0;
}

  

相關文章