一些题目

LYOI#303. 「2017 山东一轮集训 Day1」Sum

求有多少 n 位十进制数是 p 的倍数且每位之和小于等于​ i (0 <= i <= m),允许前导 0,答案对 998244353取模。

 

想法:先来个大暴力:$F[i][j][k]$表示i位,模p为j,各位之和为k的个数。转移:$F[i][j][k]->F[i-1][(j*10+l)\%p][k+l]$。

还可以这样表示:$F[x*2] [(j*10^y+l)\%p] [a+b] = F[x][j][a]* F[x][l][b] $。可以先倍增得到$F[1],F[2],F[4]....F[2^n]$,然后依次乘起来。第三维的转移是一个卷积形式,可以用NTT优化。

于是倍增+NTT解决。

Code $O(\log n*m*max(\log m,p^2))$

#include < cstdio >
#include < algorithm >

#define gec     getchar
#define FILE(F) freopen(F".in","r",stdin),freopen(F".out","w",stdout)
#define DEBUG   fprintf(stderr,"Passing [%s] in Line (%d)\n",__FUNCTION__,__LINE__);

typedef long long ll;
template
inline void read(T&x)
{
	x=0;bool f=0;char c=gec();
	for(;c<'0'||c>'9';c=gec())f=(c=='-');
	for(;c>='0'&&c<='9';c=gec())x=x*10+c-'0';
	x=f?-x:x;
}
const int LEM(4100),MP(998244353),g(3);
int n,m,p; 

void inc(int &x,int y){x+=y;x-=x>=MP?MP:0;}
void dec(int &x,int y){x-=y;x+=x<  0?MP:0;}
int min(int a,int b){return a>b?b:a;}
namespace NTT
{
	int W[LEM],R[LEM],wn,il,l,h;
	
	int power(int a,int b)
	{
		b+=b<0?MP-1:0;int t=1;
		for(;b;b>>=1){if(b&1)t=(ll)t*a%MP;a=(ll)a*a%MP;}
		return t;
	}
	void swap(int &a,int &b){int t(a);a=b;b=t;}
	
	void Pretreat(int len)
	{
		h=0;l=1;while(l<=len+len+1)l<<=1,h++; W[0]=1;
		for(int i=1;i<l;i++)R[i]=R[i>>1]>>1|(i&1)<<(h-1);
		il=power(l,MP-2);
	}
	
	void Tranform(int l,int *a,int ty)
	{
		for(int i=0;i<l;i++)
			if(i>R[i])swap(a[i],a[R[i]]);
		for(int leng=2;leng<=l;leng<<=1)
		{
			int M=leng>>1;
			wn=power(g,(MP-1)/leng*ty);
			for(int j=1;j<M;j++)W[j]=(ll)W[j-1]*wn%MP;
			for(int i=0;i<l;i+=leng)
			{
				for(int j=0;j<M;j++)
				{
					int x=a[i+j],y=(ll)a[i+j+M]*W[j]%MP;
					a[i+j]=x+y;  a[i+j+M]=x-y;
					a[i+j  ]-=a[i+j]>=MP?MP:0;
					a[i+j+M]+=a[i+j+M]<0?MP:0;			
				}
			}
		}
	}
	
	void DFT(int *a)
	{
		Tranform(l,a,1);
	}
	
	void IDFT(int *a)
	{
		Tranform(l,a,-1);
		for(int i=0;i<l;i++)a[i]=(ll)a[i]*il%MP;
	}
	
}

void Run()
{
	int y=10%p,limt=NTT::l;
	int F[p][limt],G[p][limt],T[p][limt];
	memset(F,0,sizeof(F)); memset(G,0,sizeof(G)); memset(T,0,sizeof(T));
	F[0][0]=1; for(int j=min(9,m);~j;j--) G[j%p][j]++;//if (j>m&&j<l) GG
	for(;n;n>>=1)
	{
		if(n&1)
		{
			memset(T,0,sizeof(T));
			for(int i=0;i<p;i++)NTT::DFT(F[i]),NTT::DFT(G[i]);
			for(int j=0;j<p;j++)
			 for(int l=0;l<p;l++)
		  	 {
				int Nex=(j*y+l)%p;
		 		for(int i=0;i<limt;i++) inc(T[Nex][i],(ll)F[j][i]*G[l][i]%MP);
			 }
			for(int i=0;i<p;i++)NTT::IDFT(T[i]),NTT::IDFT(G[i]);
			memcpy(F,T,sizeof(F));
			for(int i=0;i<p;i++)for(int j=m+1;j<limt;j++)F[i][j]=0;
		}
		memset(T,0,sizeof(T));
		for(int i=0;i<p;i++)NTT::DFT(G[i]);
		for(int j=0;j<p;j++)
		 for(int l=0;l<p;l++)
		 {
			int Nex=(j*y+l)%p;
			for(int i=0;i<limt;i++) inc(T[Nex][i],(ll)G[j][i]*G[l][i]%MP);//多项式加法也可以用点值加。
	 	 }
		for(int i=0;i<p;i++)NTT::IDFT(T[i]);
		memcpy(G,T,sizeof(G));  y=(ll)y*y%p;
		for(int i=0;i<p;i++)for(int j=m+1;j<limt;j++)G[i][j]=0;
	}
	for(int i=0;i<=m;i++)printf("%d ",F[0][i]),inc(F[0][i+1],F[0][i]);	
}//O(logn*m*max(logm,p^2))

int main()
{
#ifndef ONLINE_JUDGE
	FILE("C");
#endif		
	read(n);read(p);read(m);
	NTT::Pretreat(m);
	Run();
	return 0;
}

 

LYOI#304. 「2017 山东一轮集训 Day1」Set

给出 n 个非负整数,将数划分成两个集合,记为一号集合和二号集合。x1 为一号集合中所有数的异或和,x2 为二号集合中所有数的异或和。在最大化 x1+x2​​ 的前提下,最小化 x1。$ n <= 10W,数字 <= 10^18 $
 
想法:x1^x2≡x。对于x中为1的位,那么x1,x2这一位肯定有一个1,对于x1+x2贡献固定。对于x中为0的位,要么x1=x2=1,要么x1=x2=0。这里可以使用线性基贪心地取高位的1。当贪心的把x中为0的取完后,x1+x2就固定了。最大化x2就等于最小化x1,这时再贪心的填x2高位为0但在x中为1的位。
具体做法就是优先加入到x中为0的位为主元的线性基中,然后再加入为1。取答案时这样优先为0的。
Code
 
#include < cstdio >

#define gec     getchar
#define FILE(F) freopen(F".in","r",stdin),freopen(F".out","w",stdout)
#define DEBUG   fprintf(stderr,"Passing [%s] in Line (%d)\n",__FUNCTION__,__LINE__);

typedef long long ll;
template
inline void read(T&x)
{
	x=0;bool f=0;char c=gec();
	for(;c<'0'||c>'9';c=gec())f=(c=='-');
	for(;c>='0'&&c<='9';c=gec())x=x*10+c-'0';
	x=f?-x:x;
}

const int MAXN(100010);
int n;ll x[MAXN],all;
struct Linear_Base
{
	ll g[63];
	void ins(ll x,ll limt)
	{
		for(int j=62;~j;j--)
		if(((x>>j)&1)&&(!((limt>>j)&1)))
		{
			if(!g[j]){g[j]=x;return;}//一个数不能被放入两次
			else x^=g[j];
		}//优先选择0可以放1的
		for(int j=62;~j;j--)
		if(((x>>j)&1)&&((limt>>j)&1))
		{
			if(!g[j]){g[j]=x;return;}//一个数不能被放入两次
			else x^=g[j];
		}//对于相同的优先放1
	}
	
	ll Get_Max(ll x)
	{
		ll val=0;
		for(int j=62;~j;j--)
		if((!((x>>j)&1))&&(!((val>>j)&1)))
			if(g[j])val^=g[j];
		for(int j=62;~j;j--)
		if(((x>>j)&1)&&(!((val>>j)&1)))
			if(g[j])val^=g[j];//求x==1 是否能放1,贪心的放高位
		return val;
	}
}G;

ll x1,x2;
int main()
{
#ifndef ONLINE_JUDGE
	FILE("C");
#endif		
	read(n);all=0;
	for(int i=1;i<=n;i++)read(x[i]),all^=x[i];
	for(int i=1;i<=n;i++)G.ins(x[i],all);
	x2=G.Get_Max(all);
	x1=all^x2;
	printf("%lld\n",x1);
	return 0;
}

 

LYOI#306. 「2017 山东一轮集训 Day2」Pair

给出一个长度为 n 的数列 {$a_i$} 和一个长度为 m 的数列 {$b_i$},求 {$a_i$} 有多少个长度为 m 的连续子数列能与 {$b_i$} 匹配。两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对,两个数可以配对当且仅当它们的和不小于 h。

想法:$a_i+b_i \ge h <==> a_i \ge h- b_i$。令$B_i=h-b_i$后,就可以得到一个贪心做法,将两个要匹配的数列排序,一一配对,配对不上就匹配失败。然后快速判断,就可以将他们按权值大小插入一个数列里,令{$a_i$}中数为右括号,{$B_i$}中的数为左括号。一一配对等价于所有括号匹配上了。一个括号序列匹配的充要条件就是任何一个前缀左括号数大于等于右括号数。令左括号为1,右括号为-1,若一个前缀小于0,就说明不能匹配上。用线段树维护一下。

Code $O(n \log n)$

#include < algorithm >
#include < cstdio >


#define gec getchar
#define FILE(F) freopen(F".in","r",stdin),freopen(F".out","w",stdout)
#define DEBUG fprintf(stderr,"Passing [%s] in Line (%d)\n",__FUNCTION__,__LINE__);

typedef long long ll;
template
inline void read(T&x)
{
	x=0;bool f=0;char c=gec();
	for(;c<'0'||c>'9';c=gec())f=(c=='-');
	for(;c>='0'&&c<='9';c=gec())x=x*10+c-'0';
	x=f?-x:x;
}

const int MAXN(300010);
int n,m,h,Ans; 
int a[MAXN],b[MAXN];
struct AXLE
{
	int a[MAXN],up;
	void ins(int x){a[++up]=x;}
	void build()
	{
		std::sort(a+1,a+1+up);
		int _up=1;
		for(int i=2;i<=up;i++)if(a[i]!=a[i-1])a[++_up]=a[i];
		up=_up;
	}
	int Find(int x)
	{
		int l=1,r=up,mid,Ans=1;
		for(;l<=r;)if(a[mid=(l+r)>>1]<=x)l=mid+1,Ans=mid;else r=mid-1;
		return Ans;
	}
}axle;

int min(int a,int b){return a>b?b:a;}
struct SMT
{
	int nx[MAXN<<1][2],Sum[MAXN<<1],Mn[MAXN<<1],sl[MAXN<<1],sr[MAXN<<1],root,stot;
	void update(int k)
	{
		Sum[k]=Sum[nx[k][0]]+Sum[nx[k][1]];
		Mn[k]=min(Mn[nx[k][0]],Sum[nx[k][0]]+Mn[nx[k][1]]);
	}
	
	void build(int &k,int l,int r)
	{
		k=++stot; sl[k]=l; sr[k]=r; 
		if(l==r){return ;}int mid=(l+r)>>1;
		build(nx[k][0],l,mid);
		build(nx[k][1],mid+1,r);		
	}
	
	void modfiy(int k,int x,int y)
	{
		if(sl[k]==sr[k]){Sum[k]+=y;Mn[k]=Sum[k];return;}
		int mid=(sl[k]+sr[k])>>1;
		modfiy(nx[k][x>mid],x,y);
		update(k);
	}
}tree;

int main()
{
#ifndef ONLINE_JUDGE
	FILE("C");
#endif		
	read(n);read(m);read(h);
	for(int i=1;i<=m;i++)read(b[i]),b[i]=h-b[i];
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)axle.ins(a[i]);
	for(int i=1;i<=m;i++)axle.ins(b[i]);	
	axle.build();	
	for(int i=1;i<=m;i++)b[i]=axle.Find(b[i]);
	for(int i=1;i<=n;i++)a[i]=axle.Find(a[i]);
	tree.build(tree.root,1,axle.up);
	for(int i=1;i<=m;i++)tree.modfiy(tree.root,b[i],1);
	for(int l=1,r=0;r<n;)
	{
		while(r-l+1<m&&r<=n)tree.modfiy(tree.root,a[++r],-1);
		if(tree.Mn[tree.root]>=0)Ans++;
		while(r-l+1==m)tree.modfiy(tree.root,a[l++],1);
	}
	printf("%d\n",Ans);
	return 0;
}

 

LYOI#310. 「2017 山东一轮集训 Day3」第二题

 对于一棵有根树,定义一个点 u 的k-子树为 u 的子树中距离 u 不超过 k 的部分。注意,假如 u 的子树中不存在距离 u 为 k 的点,则 u 的 k-子树是不存在的。
定义两棵子树是相同的,当且仅当不考虑点的标号时,他们的形态是相同的(儿子的顺序也需要考虑)。
给定一棵 n 个点,点的标号在 [1,n],以 1 为根的有根树。问最大的 k ,使得存在两个点 u≠v,满足 u 的 k−子树与 v 的 k−子树相同。$n \le 10^5$

 

想法:看到这个「儿子的顺序也需要考虑」,所以一个树可以用括号序列表示,括号序列也可以看出01串。01串比较就可以Hash了。于是解决判断子树形态。

然后可以用显然法证明k是单调的,二分答案后。对于一个节点x,其第k+1祖先的括号序列,就要去掉x的子树这一段区间。用Vector存下每个点被去掉的区间(显然不会相交),回溯时,依次合并没有去掉的区间的Hash值。每个点最多贡献一个区间,所以总O(n)

Code $O(n \log n)$

#include < map >
#include < vector >
#include < cstdio >

#define gec getchar
#define FILE(F) freopen(F".in","r",stdin),freopen(F".out","w",stdout)
#define DEBUG fprintf(stderr,"Passing [%s] in Line (%d)\n",__FUNCTION__,__LINE__);

typedef long long ll;
typedef unsigned long long ull;
template < class T > inline void umax(T &a,T b){if(a<b)a=b;}
template < class T >
inline void read(T&x)
{
	x=0;bool f=0;char c=gec();
	for(;c<'0'||c>'9';c=gec())f=(c=='-');
	for(;c>='0'&&c<='9';c=gec())x=x*10+c-'0';
	x=f?-x:x;
}

const int MAXN(100010),Base(17271);
int n,c,x,Ans;
struct Node
{
	int nd,nx;
}bot[MAXN];int tot,first[MAXN];
void add(int a,int b)
{bot[++tot]=(Node){b,first[a]};first[a]=tot;}

int inEu[MAXN],ouEu[MAXN],cnt,num[MAXN<<1],Dep[MAXN];
void Dfs(int x)
{
	inEu[x]=++cnt; num[cnt]=1; Dep[x]=1;
	for(int v=first[x];v;v=bot[v].nx) Dfs(bot[v].nd),umax(Dep[x],Dep[bot[v].nd]+1);
	ouEu[x]=++cnt;
}

ull H[MAXN<<1],Hash[MAXN<<1];
void Get_Hash()
{
	H[0]=1;Hash[0]=0;
	for(int i=1;i<=cnt;i++)
	 H[i]=H[i-1]*Base,
	 Hash[i]=Hash[i-1]*Base+num[i];
}

bool bf;
int st[MAXN],tp;
struct Data{int l,r;};
std::vectorSeg[MAXN];
std::map<ull,int>Map;int tm;

ull Get(int l,int r)
{
	ull Tmp=Hash[r];
	Tmp-=Hash[l-1]*H[r-l+1];
	return Tmp;
}

void Dfs2(int x,int k)
{
	st[++tp]=x; 
	if(tp>=k+1)
	{
		int fff=st[tp-k];
		Seg[fff].push_back((Data){inEu[x],ouEu[x]});
	}		
	for(int v=first[x];v;v=bot[v].nx)
	{	
		Dfs2(bot[v].nd,k);	if(bf)return;
	}
	if(Dep[x]>=k)
	{
		ull val=0;int last=inEu[x];
		for(int v=0,sz=Seg[x].size();v<sz;v++)
		{
			if(last<Seg[x][v].l)
				val=val*H[ Seg[x][v].l-last ]+ Get(last,Seg[x][v].l-1);
			last=Seg[x][v].r+1;
		}
		if(last<=ouEu[x]) val=val*H[ ouEu[x]-last+1 ]+Get(last,ouEu[x]);
		if(Map[val]==tm)bf=1;
		Map[val]=tm;
	}
	tp--;
}

bool Check(int k)
{
	tp=0; tm++; bf=0; Dfs2(1,k); 
	for(int i=1;i<=n;i++)Seg[i].clear();
	return bf;
}

int main()
{
#ifndef ONLINE_JUDGE
	FILE("C");
#endif
	read(n);
	for(int i=1;i<=n;i++)
	{
		read(c);
		while(c--) read(x),add(i,x);
	}
	Dfs(1); Get_Hash();	Ans=1;
	for(int l=1,r=Dep[1],mid;l<=r;)
	if(Check(mid=(l+r)>>1))l=mid+1,Ans=mid;else r=mid-1;
	printf("%d\n",Ans-1);
	return 0;
}
posted @ 2017-06-26 19:27  Oncle_Ha  阅读(217)  评论(0编辑  收藏  举报