2018-2019 ACM-ICPC, Asia East Continent Finals部分题解

  C:显然每p2个数会有一个0循环,其中22 32 52 72的循环会在200个数中出现,找到p2循环的位置就可以知道首位在模p2意义下是多少,并且循环位置几乎是唯一的(对72不满足但可能的位置也很少)。于是这样枚举范围就直接从1e9变成了1e9/44100。然后考虑暴力求μ验证,求μ可以以O(n1/3/lnn)的时间完成,即对n1/3内的质数暴力check并除掉,剩下的直接判断是不是平方数即可。最后只要相信200个数匹配一会就break了就能过了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 210
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int prime[N],a[N],p4=-1,p9=-1,p25=-1,p49=-1,cnt=0;
inline calc(int x)
{
	for (int i=1;i<=cnt;i++)
	{
		if (x%prime[i]==0) x/=prime[i];
		if (x%prime[i]==0) return 0;
	}
	if (x==1) return 1;
	int u=sqrt(x);
	if (u*u==x||(u+1)*(u+1)==x) return 0;
	else return 1;
}
signed main()
{
	for (int i=2;i<=1000;i++)
	{
		bool flag=0;
		for (int j=2;j<i;j++)
		if (i%j==0) {flag=1;break;}
		if (!flag) prime[++cnt]=i;
	}
	for (int i=0;i<200;i++) a[i]=getc()-'0';
	for (int i=0;i<4;i++)
	{
		bool flag=0;
		for (int j=i;j<200;j+=4)
		if (a[j]!=0) {flag=1;break;}
		if (!flag) {p4=(4-i)%4;break;}
	}
	if (p4==-1) {cout<<-1;return 0;}
	for (int i=0;i<9;i++)
	{
		bool flag=0;
		for (int j=i;j<200;j+=9)
		if (a[j]!=0) {flag=1;break;}
		if (!flag) {p9=(9-i)%9;break;}
	}
	if (p9==-1) {cout<<-1;return 0;}
	for (int i=0;i<25;i++)
	{
		bool flag=0;
		for (int j=i;j<200;j+=25)
		if (a[j]!=0) {flag=1;break;}
		if (!flag) {p25=(25-i)%25;break;}
	}
	if (p25==-1) {cout<<-1;return 0;}
	int P=4*9*25*49,r;
	for (int i=0;i<49;i++)
	{
		bool flag=0;
		for (int j=i;j<200;j+=49)
		if (a[j]!=0) {flag=1;break;}
		if (!flag)
		{
			p49=(49-i)%49;	
			for (int k=0;k<P;k++)
			if (k%4==p4&&k%9==p9&&k%25==p25&&k%49==p49) {r=k;break;}
			for (int k=r;k+200<=1000000001;k+=P)
			{
				bool flag=0;
				for (int x=k;x<k+200;x++)
				if (calc(x)!=a[x-k]) {flag=1;break;}
				if (!flag) {cout<<k;return 0;}
			}
		}
	}
	cout<<-1;
	return 0;
	//NOTICE LONG LONG!!!!!
}

 

  I:考虑将增加A的贡献直接加入dp值。要算贡献需要知道之后攻击了多少次,攻击时间的和,于是设f[i][j][k]为前i次后期望之后攻击j次攻击时间和为k的最大伤害,转移显然。倒着dp和其本质相同。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 110
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int n,a[N],b[N],c[N],s[N];
ll f[2][N][N*N];
signed main()
{
	int T=read();
	while (T--)
	{
		n=read();
		memset(f,0,sizeof(f));
		for (int i=1;i<=n;i++) a[i]=read(),b[i]=read(),c[i]=read();
		s[n]=n;
		for (int i=n-1;i>=1;i--) s[i]=s[i+1]+i;
		for (int i=0;i<n;i++)
		{
			memset(f[i&1^1],0,sizeof(f[i&1^1]));
			for (int j=0;j<=n-i;j++)
				for (int k=0;k<=s[i+1];k++)
				{
					f[i&1^1][j][k]=max(f[i&1^1][j][k],f[i&1][j][k]+1ll*(k-(i+1)*j)*b[i+1]),
					f[i&1^1][j][k]=max(f[i&1^1][j][k],f[i&1][j][k]+1ll*j*c[i+1]);
					if (j&&(k>=i+1))
					{
						f[i&1^1][j-1][k-(i+1)]=max(f[i&1^1][j-1][k-(i+1)],f[i&1][j][k]+a[i+1]);
					}
				}
		}
		cout<<f[n&1][0][0]<<endl;
	}
	return 0;
	//NOTICE LONG LONG!!!!!
}

 

  J:看做一个零和博弈,那个式子看成先手的收益,相反数看成后手的收益。考虑纳什均衡,则要尽量使后手选择各后缀的收益均相同(事实上若有某后缀包含另一后缀,相同是不可能实现的,但容易发现被包含的后缀不分配概率一定最优,实际上这个分析没有任何卵用)。考虑对后缀数组以height最小值分治,这样所有跨过该位置的两后缀lcp即为该height值。递归下去算出两侧的最优答案,容易发现合并时两侧保留原本的概率分配是最优的,因为两侧自身如何分配互相之间并不相干,那么只要给两侧分配概率使后手选择两侧的收益相同即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200010
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int T,n,rk[N<<1],tmp[N<<1],sa[N],sa2[N],cnt[N],h[N],v[N],f[N][20],LG2[N];
char s[N];
void make()
{
	int m=26;
	for (int i=1;i<=n*2;i++) rk[i]=tmp[i]=0;
	for (int i=1;i<=m;i++) cnt[i]=0;
	for (int i=1;i<=n;i++) cnt[rk[i]=(s[i]-'a'+1)]++;
	for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
	for (int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;
	for (int k=1;k<=n;k<<=1)
	{
		int p=0;
		for (int i=n-k+1;i<=n;i++) sa2[++p]=i;
		for (int i=1;i<=n;i++) if (sa[i]>k) sa2[++p]=sa[i]-k;
		for (int i=1;i<=m;i++) cnt[i]=0;
		for (int i=1;i<=n;i++) cnt[rk[i]]++;
		for (int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
		for (int i=n;i>=1;i--) sa[cnt[rk[sa2[i]]]--]=sa2[i];
		for (int i=1;i<=n*2;i++) tmp[i]=rk[i];
		p=1,rk[sa[1]]=1;
		for (int i=2;i<=n;i++)
		{
			if (tmp[sa[i]]!=tmp[sa[i-1]]||tmp[sa[i]+k]!=tmp[sa[i-1]+k]) p++;
			rk[sa[i]]=p;
		}
		m=p;if (m==n) break;
	}
	for (int i=1;i<=n;i++)
	{
		h[i]=max(h[i-1]-1,0);
		while (s[i+h[i]]==s[sa[rk[i]-1]+h[i]]) h[i]++;
	}
	for (int i=1;i<=n;i++) v[i]=h[sa[i]];
	for (int i=1;i<=n;i++) f[i][0]=i;
	for (int i=2;i<=n;i++)
	{
		LG2[i]=LG2[i-1];
		if ((2<<LG2[i])<=i) LG2[i]++;
	}
	for (int j=1;j<20;j++)
		for (int i=1;i<=n;i++)
		if (v[f[i][j-1]]<v[f[min(n,i+(1<<j-1))][j-1]]) f[i][j]=f[i][j-1];
		else f[i][j]=f[min(n,i+(1<<j-1))][j-1];
}
int query(int x,int y)
{
	x++;
	return v[f[x][LG2[y-x+1]]]<v[f[y-(1<<LG2[y-x+1])+1][LG2[y-x+1]]]?f[x][LG2[y-x+1]]:f[y-(1<<LG2[y-x+1])+1][LG2[y-x+1]];
}
long double solve(int l,int r)
{
	if (l==r) return n-sa[l]+1;
	int mid=query(l,r),h=v[mid];
	long double x=solve(l,mid-1),y=solve(mid,r);
	long double k=(y-h)/(x+y-h-h);
	return x*k+h*(1-k);
}
signed main()
{
#ifndef ONLINE_JUDGE
	freopen("j.in","r",stdin);
	freopen("j.out","w",stdout);
#endif
	T=read();
	while (T--)
	{
		scanf("%s",s+1);n=strlen(s+1);
		make();
		printf("%.12f\n",(double)solve(1,n));
	}
	return 0;
	//NOTICE LONG LONG!!!!!
}

 

  K:考虑一个暴力dp,即设f[i][j]为以i为左端点要合成j时右端点至少为多少。转移显然有f[i][j]=min(f[f[i][j-1]+1][j-1],nxt[i][j]),其中nxt[i][j]为i位置之后出现的第一个j的位置。

  显然f[i][j]随i增加单调不降。这样对于询问(l,r,k),只要二分出满足f[i][k]<=r的最大i,然后答案即为(i-l+1)*(r+1)-f[l..i][k],也即要知道f数组某一段的和。

  考虑怎么不暴力地求f。观察转移,可以发现是一个类似于倍增的过程,虽然还有个取min的步骤,但仍然可以大胆猜想,对于所有j,f[i][j]构成的相同数连续段数量之和是O(nlogn)级别的。

  这样维护f数组的连续段即可。具体地,离线处理,将询问按要合成的大小从小到大排序。更新dp数组时从前往后处理,记录f的前缀和,更新完合并相邻的相同段。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200010
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int n,m,q,a[N],nxt[N],p[N],top;
ll ans[N];
struct data
{
	int l,r,x,i;ll s;
	bool operator <(const data&a) const
	{
		return x<a.x;
	}
}b[N],stk[N],tmp[N];
int getdp(int x)
{
	if (x>n) return n+1;
	int l=1,r=top,ans;
	while (l<=r)
	{
		int mid=l+r>>1;
		if (stk[mid].l<=x) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return stk[ans].x;
}
ll getdps(int x)
{
	if (x==0) return 0;
	int l=1,r=top,ans=0;
	while (l<=r)
	{
		int mid=l+r>>1;
		if (stk[mid].r<=x) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return stk[ans].s+1ll*(x-stk[ans].r)*stk[ans+1].x;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("k.in","r",stdin);
	freopen("k.out","w",stdout);
#endif
	n=read(),m=read(),q=read();
	for (int i=1;i<=n;i++) a[i]=read();
	for (int i=1;i<=q;i++) b[i].l=read(),b[i].r=read(),b[i].x=read(),b[i].i=i;
	sort(b+1,b+q+1);
	for (int i=1;i<=m;i++) p[i]=n+1;
	for (int i=n;i>=1;i--) nxt[i]=p[a[i]],p[a[i]]=i;
	top=1;stk[1].l=1,stk[1].r=n,stk[1].x=n+1,stk[1].s=1ll*(n+1)*n;
	int cur=0;
	for (int i=1;i<=m;i++)
	{
		int x=p[i],top_tmp=0;
		for (int j=1;j<=top;j++)
		{
			int y=getdp(stk[j].x+1),last=stk[j].l-1;
			while (stk[j].r>=x)
			{
				top_tmp++;
				tmp[top_tmp].l=last+1,tmp[top_tmp].r=x,tmp[top_tmp].x=x;
				tmp[top_tmp].s=tmp[top_tmp-1].s+1ll*(x-last)*x;
				last=x;x=nxt[x];
			}
			if (last<stk[j].r)
			{
				top_tmp++;
				tmp[top_tmp].l=last+1,tmp[top_tmp].r=stk[j].r,tmp[top_tmp].x=min(x,y);
				tmp[top_tmp].s=tmp[top_tmp-1].s+1ll*(stk[j].r-last)*min(x,y);
			}
		}
		top=0;
		for (int j=1;j<=top_tmp;j++)
		{
			int t=j;
			while (t<top_tmp&&tmp[t+1].x==tmp[j].x) t++;
			top++;stk[top].l=tmp[j].l,stk[top].r=tmp[t].r,stk[top].x=tmp[j].x,stk[top].s=tmp[t].s;
			j=t;
		}
		for (int j=1;j<=top;j++) stk[j].s=stk[j-1].s+1ll*(stk[j].r-stk[j].l+1)*stk[j].x;
		while (b[cur+1].x==i)
		{
			cur++;
			int l=1,r=top,k=0;
			while (l<=r)
			{
				int mid=l+r>>1;
				if (stk[mid].x<=b[cur].r) k=stk[mid].r,l=mid+1;
				else r=mid-1;
			}
			if (k>=b[cur].l) ans[b[cur].i]=1ll*(k-b[cur].l+1)*(b[cur].r+1)-(getdps(k)-getdps(b[cur].l-1));
		}
		//for (int j=1;j<=top;j++) cout<<stk[j].l<<' '<<stk[j].r<<' '<<stk[j].x<<' '<<stk[j].s<<endl;cout<<endl;
	}
	for (int i=1;i<=q;i++) printf("%lld\n",ans[i]);
	return 0;
}
 

 

  

 

posted @ 2019-04-16 21:49  Gloid  阅读(528)  评论(0编辑  收藏  举报