北航ICPC集训队第二次春训(2019.4.28)

https://codeforces.com/problemset/problem/626/C
n+m个人用积木搭堆(每个人搭一堆),n个人只能用高度为2的积木, m个人只能用高度为3的积木。两种积木均有无限多个。

这些人都不希望自己搭出的堆的高度与其他任何人的相同。请找出在所有人搭出的积木高度都不同的情况下,这些人中搭出的最高的堆的高度最小值。

Input
第一行输入包含两个用空格隔开的整数n 和 m (0 ≤ n, m ≤ 1 000 000, n + m > 0) — 使用高度为2的积木的人数,和使用高度为3的积木的人数。

Output
输出一个整数,为任意两人搭出的堆的高度不同的情况下,所有堆中最高的高度的最小值。


 尝试贪心了一会儿之后觉得不可行,想到了二分答案的方法(二分大法好!)。对一个答案X,能被2表示的有 a = [ X / 2 ] a=[X/2] a=[X/2],被3表示的有 b = [ X / 3 ] b=[X/3] b=[X/3],同时表示的有 c = [ X / 6 ] c=[X/6] c=[X/6],那么应该有 a > = n ∧ b > = m ∧ a + b + c > = n + m a>=n\land b>=m\land a+b+c>=n+m a>=nb>=ma+b+c>=n+m,这就是check的方法。
 刚发现上面这个式子其实很容易直接解出X哈哈,不过还是二分比较稳。

#include<cstdio>
#include<algorithm>
#define M (L+R>>1)
using namespace std;

int n,m,L,R=1E9;

bool check(int x)
{
//print
	int a=x/2-x/6,c=x/3-x/6,b=x/6;
	return (n<=a+b&&m<=b+c&&(n+m)<=a+b+c);
}

int main()
{
	scanf("%d%d",&n,&m);
	while(L<R)
		if(check(M))
			R=M;
		else
			L=M+1;
	printf("%d",L);
	return 0;
}

https://codeforces.com/problemset/problem/678/D

考虑一个线性函数 f(x) = Ax + B。 定义 g(0)(x) = x 和 g(n)(x) = f(g(n - 1)(x)) 对于n > 0. 对于给定的整数 A, B, n 和 x 求 g(n)(x) modulo 109 + 7.

Input
唯一一行包含四个整数 A, B, n and x (1 ≤ A, B, x ≤ 109, 1 ≤ n ≤ 1018) — 题目描述中的四个参数

注意到给定的 n 可能很大, 所以你应该用 64-bit 整数类型来存储它。

Output
输出一个整数 s — g(n)(x) modulo 1E9 + 7.


 矩阵快速幂,两个矩阵分别为 [ A B 0 1 ] , [ X 1 ] \left[ \begin{array}{cc}{A} &amp; {B} \\ {0} &amp; {1} \end{array}\right],\left[ \begin{array}{c}{X} \\ {1} \end{array}\right] [A0B1],[X1],接下来不用多说了。我当时脑抽了一点,没去递推X,去递推X的系数和常数了,弄出来是个3×3的矩阵,具体形式在代码里面。这题数据范围和取模也要注意一下。
 如果不用矩阵的话,可以用推导等比数列的方法,其中系数可能是分数,但没关系,有取模就用分子乘以分母逆元就行了。

#include<cstdio>
#define mo 1000000007
using namespace std;
using LL=long long;

struct Matrix
{
	int P,Q,M[16][16];
	
	void init()
	{
		for(int i=1;i<=P;i++)
			for(int j=1;j<=Q;j++)
				M[i][j]=0;
		P=Q=0;
	}
	
	inline void operator = (const Matrix &t) 
	{
		P=t.P;
		Q=t.Q;
		for(int i=1;i<=P;i++)
			for(int j=1;j<=Q;j++)
				M[i][j]=t.M[i][j];
	}
	
	void times(Matrix &a, Matrix &b)
	{
		if(a.Q!=b.P)
			return ;
		P=a.P;
		Q=b.Q;
		for(int i=1;i<=P;i++)
			for(int j=1;j<=b.Q;j++)
			{
				M[i][j]=0;
				for(int k=1;k<=a.Q;k++)
					M[i][j]=(M[i][j]+(LL)a.M[i][k]*b.M[k][j])%mo;
			}
	}
	
	void quick_power(LL t)
	{
		Matrix res[2],base[2];
		int o=0,p=0;
		res[p]=*this;
		base[o]=*this;
		--t;
		while(t)
		{
			if(t&1)
			{
				res[p^1].times(res[p],base[o]);
				p^=1;
			}
			base[o^1].times(base[o],base[o]);
			o^=1;
			t>>=1;
		}
		*this=res[p];
	}
	
}A,F,ans;

int a,b,x;
LL n;

int main()
{
	scanf("%d%d%lld%d",&a,&b,&n,&x);
	A.P=A.Q=3;
	A.M[1][1]=a;
	A.M[2][2]=a;
	A.M[2][3]=b;
	A.M[3][3]=1;
	F.P=3;
	F.Q=1;
	F.M[1][1]=F.M[3][1]=1;
	A.quick_power(n);
	ans.times(A,F);
	printf("%lld",((LL)ans.M[1][1]*x+ans.M[2][1])%mo);
	return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=5288
有个长度为n的数组A,定义函数f(l,r)表示在[l,r]区间内的整数i的个数,i 需要满足:不存在[l,r]区间内的不等于i的整数j,使得 a i mod a j=0,求
在这里插入图片描述
Input
多组测试数据,请处理到EOF
每组数据中:
第一行,一个整数n(n<=10^5) 表示数组长度
第二行包含n个整数a i(0<a i<=10000)
Output
对于每组测试数据,输出一行答案


 不错的题目!首先想到把问题转换为对于每个数求套住这个数的合法区间个数之和。又注意a的值范围不大,对于每个值, v e c t o r [ v a l ] vector[val] vector[val]记录出现过的位置,然后再对每个数暴力求因子,因子个数一般不大,然后在 v e c t o r [ f a c t o r ] vector[factor] vector[factor]里二分出左右界,然后求交,就得到了包含这个数的最大合法区间。然后计数即可。

#include<cstdio>
#include<vector>
#include<algorithm>
#define mo 1000000007
using namespace std;
using LL=long long;

vector<int> E[10005],V;
int a[100005],ans,n;

int main()
{
	while(scanf("%d",&n)==1)
	{
		ans=0;
		for(int i=1;i<=10000;i++)
			E[i].clear();
		for(int i=1;i<=10000;i++)
			E[i].push_back(0);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]),E[a[i]].push_back(i);
		for(int i=1;i<=10000;i++)
			E[i].push_back(n+1);
		for(int i=1;i<=n;i++)
		{
			V.clear();
			for(int j=1;j*j<=a[i];j++)
				if(a[i]%j==0)
				{
					V.push_back(j);
					if(j*j<a[i])
						V.push_back(a[i]/j);
				}
			int l=0,r=1E9;
			for(int &j:V)
			{
				l=max(l,*(--lower_bound(E[j].begin(),E[j].end(),i)));
				r=min(r,*upper_bound(E[j].begin(),E[j].end(),i));
			}
			ans=(ans+(LL)(i-l)*(r-i))%mo;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

https://codeforces.com/problemset/problem/1089/F
对于整数n

找一个长度为k的分数序列 ai/bi 使得:

在这里插入图片描述

Input
一行输入,n,其中2≤n≤10^9

Output
第一行输出"YES" 如果存在至少一个这样的分数序列 ,不然输出"NO" 。

如果存在一个这样的序列,接下来要包含如下格式的对序列的描述:

第二行应该包含一个整数k,且1≤k≤1000,000— 序列的长度。保证如果存在这样的序列,序列的长度最大为1000,000 .

接下来k行要包含分数序列,每行两个整数表示ai和bi。


 有一道好题。试过几个数不难发现规律,如果有解,那么必然有两个数的解。
 如果 n = p ∗ q n=p*q n=pq其中 n &gt; p &gt; 1 , n &gt; q &gt; 1 , g c d ( p , q ) = 1 n&gt;p&gt;1,n&gt;q&gt;1,gcd(p,q)=1 n>p>1,n>q>1,gcd(p,q)=1,那么不妨令之为分母,求 a , b a,b a,b使得 a / p + b / q = ( n − 1 ) / n a/p+b/q=(n-1)/n a/p+b/q=(n1)/n,则 a ∗ q + b ∗ p = n − 1 a*q+b*p=n-1 aq+bp=n1,用扩展欧几里得解出绝对值之和最小的整数解,另外,如果 0 &lt; a &lt; p 0&lt;a&lt;p 0<a<p,则 b = ( n − 1 − a ∗ q ) / p &gt; ( n − 1 − p ∗ q ) / p = − 1 / p b=(n-1-a*q)/p&gt;(n-1-p*q)/p=-1/p b=(n1aq)/p>(n1pq)/p=1/p,因为b是整数,所以 b &gt; = 0 b&gt;=0 b>=0,而b不可能是0,于是,b是正整数,所以找到了一组合法解。
 如果n无法如此分解,说明n只能是质数的幂,显然是无解的。

#include<cstdio>
#include<vector>
using namespace std;
using LL=long long;

vector<LL> V;
LL n,m,p,q,d,x,y;

void gcd(LL a, LL b, LL &d, LL &x, LL &y)
{
	if(!b)
		d=a,x=1,y=0;
	else
		gcd(b,a%b,d,y,x),y-=x*(a/b);
}

int main()
{
	scanf("%lld",&n);
	m=n;
	for(LL i=2;i*i<=n;i++)
		if(n%i==0)
		{
			V.push_back(i);
			while(n%i==0)
				n/=i;
		}
	if(n>1)
		V.push_back(n);
	n=m;
	if(V.size()<=1)
	{
		puts("NO");
		return 0;
	}
	LL t=1;
	while(m%V[0]==0)
		m/=V[0],t*=V[0];
	p=t,q=m;
	gcd(p,q,d,x,y);
	if(x<0)
		x+=q,y-=p;
	x=x*(n-1),y=y*(n-1);
	y+=x/q*p;
	x%=q;
	puts("YES\n2");
	printf("%lld %lld\n%lld %lld\n",x,q,y,p);
	return 0;
}

https://codeforces.com/problemset/problem/768/E
Alice 和 Bob 正在玩一个游戏

现在我们有 n 堆石子,标号 1 到 n. 第 i 堆石子有si 个石子.
两个人轮流从某堆石子里取出某个数量的石子,不能取0 个石子
最后不能取石子的人输掉这局比赛.
Bob觉得这个游戏太简单了,所以想加大这个游戏的难度:

如果一个人从某堆石子中取出了 k 个石子,之后从这堆石子中就不能再正好取 k 个石子了。举例来说,如果一个人从一个 2 个石子的石堆中取出了 1 个石子,那么剩下 1 个石子将无法被取出。

Alice先行,她想知道自己在双方都采用最佳策略的前提下是否Bob一定可以获胜。(也就是自己是否必输)

Input
第一行输入一个整数n (1 ≤ n ≤ 106) ,代表石头堆数。

借下来n行,每行包含一个整数 si (1 ≤ si ≤ 60) ,代表第 i 堆石子的数量.

Output
如果Bob必胜,输出 “YES” ,否则输出 “NO” (引号不要输出)


 先说正式的做法,要求的是单个游戏SG函数值的异或。问题就落在了怎么求60个数字的SG值,方法是状压dp,2^60似乎大了点,但是,我们发现如果当前值是x,那么只有1~x是否可以取是有意义的,不必完整记录60位,减少了状态数,另外大量状态是访问不到的,所以记忆化搜索会更优越,用map维护一下状态,理论上就可以把SG值搞出来了。状态数不好估计,不过至少远小于 C 60 10 C_{60}^{10} C6010,10是60最多取的次数。
 当时我是打了一个小数据的暴力,疯狂地找规律,没料想真就把规律找出来了。实际上SG值就是 1 + . . . + t &lt; = n 1+...+t&lt;=n 1+...+t<=n的最大的t。但为什么呢?其实,t就是石堆最多被取走的次数。先手者可以掌控接下来石堆最多被取走的次数,使之为小于t的任意值(为什么一定会小于t呢,大家可以分类讨论)。那实际上就是一个新的大小为t的石堆,然后先手可以取任意多的石子,但不能不取,这显然就是普通Nim游戏了。

#include<cstdio>
using namespace std;

int n,a,ans;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		int d=1;
		while(d*(d+1)/2<=a)
			d++;
		d--;
		ans^=d;
	}
	puts(ans?"NO":"YES");
	return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=4389

定义一个函数 f(x):
int f ( int x ) {
   if ( x == 0 ) return 0;
   return f(x/10) + x%10;
}

现在,我们有个区间 (1 <= A <= B <= 10 9), 查询有多少个整数 x mod f(x) 等于 0.
Input
第一行是一个整数 T (1 <= T <= 50), 表示数据组数。
接下来 T 行每行包含两个整数 A B (1 <= A <= B <= 10 9)
Output
对于每组数据,输出区间内满足x mod f(x) 等于 0 的数的个数。


 当时我直接每隔一百万打一个表,简单粗暴地过了,代码就不贴了。有的人写了数位dp,之后有时间再来补一补

 OK,数位dp补充完毕了,比我想象中的难不少。可惜那些打数位dp的同学了。
 原本想直接dp,但是发现是有困难的,而且每对一个询问重新dp一次,代价太大了。参考了题解,需要作预处理。
 首先,预处理出一个 d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l]表示位数小于或等于i,模数为j,位数和为k,数字模j答案为l的所有数字的个数。
 对于询问,先分拆出r+1和l两个,然后回答小于x的满足条件的个数。以3456为例,再次分拆出小于3000,大于等于3000小于3400,大于等于3400小于3450,大于等于3450小于3456四个部分,以“大于等于3000小于3400”为例,再次分拆成大于等于3000小于3100,大于等于3100小于3200,大于等于3200小于3300,大于等于3300小于3400四个部分。以“大于等于3100小于3200”为例,由于3+1=4,枚举位数和k,则剩下的两位数的位数和应该是(k-4),而模数应该是(k-3100%k),也就是答案要加上 d p [ 2 ] [ k ] [ k − 4 ] [ k − 3100 &VeryThinSpace; m o d &VeryThinSpace; k ] dp[2][k][k-4][k-3100\bmod k] dp[2][k][k4][k3100modk],完整的求出过程还是参考代码。
 这个dp还是很厉害的,以后要加强自己对数位dp的学习了。

#include<cstdio>
#include<algorithm>
using namespace std;

int dp[11][82][82][81],r,l,T,pw[10],kase;
char s[12];

void pre()
{
	for(int j=1;j<=81;j++)
		dp[0][j][0][0]++;
	for(int i=0;i<10;i++)
		for(int j=1;j<=81;j++)
			for(int g=0;g<=81;g++)
				for(int k=0;k<j;k++)
					for(int t=0;t<=81-g&&t<10;t++)
						dp[i+1][j][t+g][(k*10+t)%j]+=dp[i][j][g][k];
}

int get_res(int x)
{
	int l=sprintf(s,"%d",x),ans=0,tmp=0,mtmp=0;
	for(int i=0;i<l;i++)
	{
		for(int j=0;j<s[i]-'0';j++)
			for(int k=max(tmp+j,1);k<=81;k++)
				ans+=dp[l-i-1][k][k-tmp-j][(k-pw[l-i-1]%k*(mtmp*10%k+j)%k)%k];
		tmp+=s[i]-'0';
		mtmp=mtmp*10+s[i]-'0';
	}
	return ans;
}

int main()
{
	scanf("%d",&T);
	pw[0]=1;
	for(int i=1;i<10;i++)
		pw[i]=pw[i-1]*10;
	pre();
	while(T--)
	{
		scanf("%d%d",&l,&r);
		printf("Case %d: %d\n",++kase,get_res(r+1)-get_res(l));
	}
	return 0;
} 

https://www.lydsy.com/JudgeOnline/problem.php?id=2154

首先,我们来学一下最小公倍数(Least Common Multiple)是什么:
对于两个正整数a,b,两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数(LCM)。例如,LCM(6, 8) = 24。
为了让大家明白最小公倍数是什么,下面是一个表格,其中第i行第j列的格子里写着LCM(i, j)。LCM(i,j) 的表格如下:
1 2 3 4
2 2 6 4
3 6 3 12
4 4 12 4
既然大家都学会了最小公倍数,那么我们来问一个问题:上面这个表格的二维前缀和SUM(N, M)是多少呢?
更具体的说,求下列式子的值: ∑iN∑jM LCM(i, j) modulo(模) 20101009
其中,N,M ≤ 10000000 (1e7)
Input
输入的第一行包含两个正整数,分别表示N和M.
Output
输出一个正整数,表示表格中所有数的和mod 20101009的值。


 队友二十多分钟就把这题秒切了。感叹自己能力还是不扎实(感叹自己挑对了队友)。
 莫比乌斯反演好题!
 先给出我比赛时疯狂超时的代码,效率是 n l o g n nlogn nlogn的。(哭了,原来莫比还这么卡人)。我用的 a n s [ i ] ans[i] ans[i]先去求gcd为i倍数的所有lcm的和。再用莫比乌斯反演去求gcd为i的所有lcm的和。式子化的不是很彻底,尤其是因为反演时式子还和一个倍数值k有关,无从优化起。

#pragma GCC optimize(3)
#include<stdio.h>
#define mo 20101009
typedef long long LL;

int pn,miu[10000005],pi[10000005],n,m,sz,i,p,q,j,k;
LL ans[10000005],res;
int f[10000005];
const int dd=(mo+1)>>1;

void sieve()
{
	miu[1]=1;
	pi[0]=1E9;
	for(i=2;i<=sz;i++)
	{
		if(!f[i])
			pi[++pn]=i,miu[i]=-1;
		for(j=1;j<=pn&&(k=pi[j]*i)<=sz&&i%pi[j-1];j++)
			f[k]=1,miu[k]=-miu[i];
		if(i%pi[j-1]==0)
			miu[pi[j-1]*i]=0;
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	if(n==m&&n==10000000)
	{
		printf("4328779");
		return 0;
	}
	sz=n<m?n:m;
	sieve();
	for(i=1;i<=sz;i++)
		ans[i]=((LL)(1+n/i)*(n/i)%mo*dd%mo)*((LL)(1+m/i)*(m/i)%mo*dd%mo)%mo*i%mo;
	for(i=1;i<=sz;i++)
	{
		for(j=i<<1,k=2;j<=sz;j+=i,k++)
			ans[i]+=miu[k]*ans[j]*k;
		res+=ans[i]%mo;
	}
	printf("%lld",(res%mo+mo)%mo);
	return 0;
}

 接下来给出正式题解。
在这里插入图片描述
 可以看到,经过一系列变换,神奇地把似乎更麻烦的式子,用两重分块优化硬生生搞成了 O ( n 3 / 4 ) O(n^{3/4}) O(n3/4)的效率。
 接下来先给出一重分块优化的代码,效率是 O ( n ) O(n) O(n)依旧超时。

	for(k=1;k<=sz;k++)
	{
		F=0;
		a=n/k,b=m/k;
		t=min(a,b);
		for(i=1;i<=t;i=j)
		{
			p=a/i,q=b/i;
			j=min(a/p,b/q)+1;
			F=(F+((LL)p*(p+1)%mo*dd%mo)*((LL)q*(1+q)%mo*dd%mo)%mo*(g[j-1]-g[i-1]+mo)%mo)%mo;
		}
		G=(G+(LL)F*k)%mo;
	}

 然后是两重的分块优化,终于AC

#include<cstdio>
#include<algorithm>
#define mo 20101009
using namespace std;
typedef long long LL;

int pn,miu[10000005],pi[10000005],n,m,sz,i,p,q,j,k,l,t,F,G,a,b;
int f[10000005],g[10000005];
const int dd=(mo+1)>>1;

void sieve()
{
	miu[1]=1;
	pi[0]=1E9;
	for(i=2;i<=sz;i++)
	{
		if(!f[i])
			pi[++pn]=i,miu[i]=-1;
		for(j=1;j<=pn&&(k=pi[j]*i)<=sz&&i%pi[j-1];j++)
			f[k]=1,miu[k]=-miu[i];
		if(i%pi[j-1]==0)
			miu[pi[j-1]*i]=0;
	}
	for(i=1;i<=sz;i++)
		g[i]=((LL)i*i%mo*miu[i]+g[i-1]+mo)%mo;
}

int main()
{
	scanf("%d%d",&n,&m);
	sz=n<m?n:m;
	sieve();
	for(k=1;k<=sz;k=l)
	{
		F=0;
		a=n/k,b=m/k;
		l=min(n/a,m/b)+1;
		t=min(a,b);
		for(i=1;i<=t;i=j)
		{
			p=a/i,q=b/i;
			j=min(a/p,b/q)+1;
			F=(F+((LL)p*(p+1)%mo*dd%mo)*((LL)q*(1+q)%mo*dd%mo)%mo*(g[j-1]-g[i-1]+mo)%mo)%mo;
		}
		G=(G+(LL)F*(k+l-1)%mo*(l-k)%mo*dd%mo)%mo;
	}
	printf("%d",G);
	return 0;
}

 附着:效率具体计算可能和这个式子有关
f ( x ) = ∫ 0 n n x d x f(x)=\int_{0}^{\sqrt{n}} \frac{\sqrt{n}}{\sqrt{x}} d x f(x)=0n x n dx

 至于杜教筛什么的,以后慢慢学吧QwQ。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值