HEOI-2012 DAY1 题解 (BZOJ 2742-2744)

Pro 1:

【题目大意】

给出一个一元n 次方程:  a0 + a1x + a2x2 +…+ anxn= 0 求此方程的所有有理数解。 

Input

第一行一个整数 n。第二行n+1 个整数,分别代表a0 到an

Output

第一行输出一个整数 t,表示有理数解的个数。 
接下来t 行,每行表示一个解。 
解以分数的形式输出,要求分子和分母互质,且分母必须是正整数。 
特殊的,如果这个解是一个整数,那么直接把这个数输出。 
等价的解只需要输出一次。 
所有解按照从小到大的顺序输出。

Sample Input

3
-24 14 29 6

Sample Output

3
-4
-3/2
2/3

Hint

对于30%的数据,n<=10 
对于100%的数据,n <= 100,|ai| <= 2*107,an≠ 0


【分析】

题目中有一点很关键,那就是求有理数解,也即x=q/p的解。由复系数多项式基本定理可以知道,原多项式一定有一个(x-q/p)因式。又考虑到本题所有系数都是整数,原因式等价于(px-q)。也就是说,原方程的任何有理数根都必须对应一个(px-q)这样一个因式。

我们设原多项式为:
f(x) =a0 + a1x + a2x2 +…+ anxn
把所有的有理数解对应的因式分离出来:
f(x)=(p1*x-q1)(p2*x-q2)(p3*x-q3)...(pk*x-qk)*g(x) 

其中g(x)是不含有理数根的部分。
由韦达定理我们可以得到:
an = p1*p2*p3*...*pk*u
a0 = q1*q2*q3*...*qk*v

其中u,v分别为g(x)的最高次项系数和常数项。
由上式可以很明显地推出:对于一个有理数解q/p,有p|an,且q|a0

于是我们先用O(sqrt(a))的时间求出a0和an所有的约数。
然后直接枚举p,q的所有组合,带入原方程检验就行了。

于是又有一个难题来了,如何检验呢??

我们将q/p代入原方程后进行通分,得到下面这个式子:
f(q/p) =[ a0*p^n + a1*q*p^(n-1) + a2*q^2*p*(n-2) + ... + an*q^n ] / p^n 
分子x= Σai*q^i*p^(n-i)。
我们只需判定x==0的真假,就可以知道f(q/p)是否为0。

用高精度不仅麻烦而且容易超时,于是我们可以采用取模的方法来避免高精度。

我们选用一个较大的质数(我选择的是十亿零七),然后带模从头到尾运算,如果最后答案为0。

我们便可以近似地认为原式为0了(觉得不保险可以多选用几个质数同时检验)。


【代码】

/***********************
    ID:Ciocio
	LANG:C++
	DATE:2014-1-28
	TASK:Math homework
************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

#define MAXN 105
#define MAXD 4000
#define MODER 1000000007

typedef long long LL;
int N,ans,A[MAXN];
int a[MAXD],b[MAXD],lena,lenb;
int pow_a[MAXD],pow_b[MAXD];
struct node{
	int a,b;        //ans=b/a
	bool neg;
	bool operator<(const node &A)const{
		if(neg&&(!A.neg)) return true;
		if((!neg)&&A.neg) return false;
 		if(!neg) return b*A.a<A.b*a;
		else return b*A.a>A.b*a;
	}
	void print()
	{
		if(neg) printf("-");
		if(b%a) printf("%d/%d\n",b,a);
		else printf("%d\n",b/a);
	}
}Ans[MAXN];

int _gcd(int a,int b){return b?_gcd(b,a%b):a;}

void _init()
{
	scanf("%d",&N);
	for(int i=0;i<=N;i++) scanf("%d",&A[i]);
}

void _getson(int *v,int x,int &len)
{
	x=abs(x);
	int n=(int)(sqrt((double)(x)));
	for(int i=1;i<=n;i++)
	    if(x%i==0)
		{
			v[++len]=i;
			v[++len]=x/i;
		}
	if((x%n==0)&&(x/n==n)) len--;
}

bool _check(bool neg,LL a,LL b)   //  to confirm whether "sigma(Ai*b^(i)*a^(N-i))/a^(N)" is equal to 0
{
	LL delta,sum=0;
	pow_a[0]=pow_b[0]=1;
	for(int i=1;i<=N;i++) 
	{
		pow_a[i]=(pow_a[i-1]*a)%MODER;
		pow_b[i]=(pow_b[i-1]*b)%MODER;
	}
	for(int i=0;i<=N;i++)
	{
		delta=A[i];
		delta=(delta*pow_b[i])%MODER;
		delta=(delta*pow_a[N-i])%MODER;
		if(neg&&(i&1==1)) sum=(sum-delta+MODER)%MODER;
		else sum=(sum+delta)%MODER;
	}
	return sum==0;
}

void _solve()
{
	int p=0;
	while(A[p]==0) p++;
	if(p)                                 //特殊判断a0,a1,a2...结尾一大坨0的情况
	{
		N-=p;
	    for(int i=0;i<=N;i++) A[i]=A[i+p];            //去除这些0
	    Ans[++ans]=(node){5,0,false};               //并加入0这个解
    }
	_getson(a,A[0],lena);             //分解因数
	_getson(b,A[N],lenb);
	for(int i=1;i<=lenb;i++)
		for(int j=1;j<=lena;j++)
		{
			int x=b[i],y=a[j];
			if(_gcd(x,y)==1)          //避免重复
			{
				if(_check(0,x,y)) Ans[++ans]=(node){x,y,false};    // 将正负的y/x分别代入检验
				if(_check(1,x,y)) Ans[++ans]=(node){x,y,true};
			}
		}
	sort(Ans+1,Ans+ans+1);    //排序后再输出
	cout<<ans<<endl;
	for(int i=1;i<=ans;i++) Ans[i].print();
}

int main()
{
	_init();
	_solve();
	return 0;
}



Pro 2:

【题目大意】

每次询问一段区间内“颜色数不小于2”的颜色种类数。

n朵花,c种颜色,m次询问  

时限:5s

Input

第一行四个空格隔开的整数n、c 以及m。 
接下来一行n 个空格隔开的整数,每个数在[1, c]间,第i 个数表示第i 朵花的颜色。 
接下来m 行每行两个空格隔开的整数l 和r(l ≤ r),表示一次询问

Output

共m 行,每行一个整数,第i 个数表示第i次询问的答案

Sample Input

5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5

Sample Output

2
0
0
1
0

Hint

【样例说明】 
询问[1, 5]:颜色为1 和2 
询问[1, 2]:0
询问[2, 2]:0
询问[2, 3]:颜色2 ; 
询问[3, 5]:0
【数据范围】 
对于20%的数据,n ≤ 10^2,c ≤ 10^2,m ≤ 10^2; 
对于50%的数据,n ≤ 10^5,c ≤ 10^2,m ≤ 10^5; 
对于100%的数据,1 ≤ n ≤10^6,c ≤ n,m ≤ 10^6。


【分析】

水题~~~~

从左往右记录下每种颜色第一次出现的位置,和每个位置的下一个同色的位置。
然后把所有提问按照左端点升序排序;

在树状数组中把每一类颜色的第二次出现处标上1,维护前缀和;
用一个变量k向右移动,保证每时每刻都是:从k起向右,每一类的第二次出现的位置为1,其他为0
对于每个提问,当k==左边界的时候,求一次1到右端点的前缀和就是答案。


【代码】

/***********************
    ID:Ciocio
	LANG:C++
	DATE:2014-1-19
	TASK:flower
************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

#define MAXN 1000005
#define lowbit(x) ((x)&(-(x)))

int N,C,M;
int v[MAXN],Next[MAXN],Last[MAXN],bit[MAXN];
struct node{
	int l,r,ans,rank;
	void print(){printf("%d\n",ans);}
};
node Q[MAXN];
bool _cmp1(const node &A,const node &B){return (A.l<B.l)||(A.l==B.l&&A.r<B.r);}
bool _cmp2(const node &A,const node &B){return A.rank<B.rank;}

void _read(int &x)
{
	char tt=getchar();
	while(tt<'0'||'9'<tt) tt=getchar();
	for(x=0;'0'<=tt&&tt<='9';x=(x<<1)+(x<<3)+tt-'0',tt=getchar());
}

void _init()
{
	_read(N);_read(C);_read(M);
	for(int i=1;i<=N;i++) _read(v[i]);
	for(int i=1;i<=M;i++)
	{
		Q[i].rank=i;
		_read(Q[i].l);_read(Q[i].r);
	}
	sort(Q+1,Q+M+1,_cmp1);
}

void _modify(int x,int delta){for(;x<=N;x+=lowbit(x)) bit[x]+=delta;}

int _getsum(int x)
{
	int rt=0;
	for(;x;x-=lowbit(x)) rt+=bit[x];
	return rt;
}

void _solve()
{
	for(int i=N;i;i--)
	{
		Next[i]=Last[v[i]];
		Last[v[i]]=i;
	}
	for(int i=1;i<=C;i++) if(Last[i]!=0&&Next[Last[i]]!=0) _modify(Next[Last[i]],1);
	for(int i=1,k=1;i<=M;i++)
	{
		while(k!=Q[i].l&&k<=N)
		{
		    if(Next[k]!=0) _modify(Next[k],-1);
			if(Next[Next[k]]!=0) _modify(Next[Next[k]],1);
			k++;
		}
		Q[i].ans=_getsum(Q[i].r);
	}
	sort(Q+1,Q+M+1,_cmp2);
	for(int i=1;i<=M;i++)  Q[i].print();
}

int main()
{
	_init();
	_solve();
	return 0;
}


Pro 3:

【题目大意】

时限:20s 。 
两个国家看成是A,B 两国,现在是两个国家的描述: 
1、A 国:每个人都有一个友善值,当两个A 国人的友善值a、b,如果a xor b mod 2=1, 那么这两个人都是朋友,否则不是; 
2、B 国:每个人都有一个友善值,当两个B 国人的友善值a、b,如果a xor b mod 2=0 或者 (a | b)化成二进制有奇数个1,那么两个人是朋友,否则不是朋友; 
3、A、B 两国之间的人也有可能是朋友,数据中将会给出A、B 之间“朋友”的情况。 
4、对于朋友的定义,关系是是双向的。 
在AB 两国,朋友圈的定义:一个朋友圈集合S,满足 S∈A ∪ B,对于所有的i,j∈S,i 和j 是朋友 
求最大朋 友圈的人数

Input

第一行t<=6,表示输入数据总数。 
接下来t 个数据: 
第一行输入三个整数A,B,M,表示A 国人数、B 国人数、AB 两国之间是朋友的对数; 
第二行A 个数ai,表示A 国第i 个人的友善值; 
第三行B 个数bi,表示B 国第j 个人的友善值; 
第4——3+M 行,每行两个整数(i,j),表示第i 个A 国人和第j 个B 国人是朋友。

Output

输出t 行,每行,输出一个整数,表示最大朋友圈的数目。

Sample Input

1
2 4 7
1 2
2 6 5 4
1 1
1 2
1 3
2 1
2 2
2 3
2 4

Sample Output

5

Hint

【样例说明】 
最大朋友圈包含A 国第1、2 人和B 国第1、2、3 人。 
【数据范围】 
对于其中30%的数据,A=0,B<=100; 
对于其中50%的数据,A<=10,B<=100; 
对于其中10%的数据,A<=5,B<=1000; 
对于其中10%的数据,A<=5,B<=1500; 
对于100%的数据,A<=100,B<=1500,M<=A*B,友善值在2^30 以内。


【分析】

有一点是很明显但也很重要的,那就是:最后的答案中A中的人至多只能选择2个。原因很简单,3及其以上的人数,不可能满足两两是好友的条件(两两亦或后必有0)。

那么A中就有3种情况:一个也没有,有一个,和有两个。

枚举这三种情况中,并枚举每次选择的组合。然后对于这些组合关联的B中的人,再想办法求出答案。

这里有一个定理要注意:最大团=补图的最大独立集。

一般图上的最大独立集是NP问题,而二分图上的最大独立集只需要O(NM)的复杂度,于是我们看看B中是否有二分图的性质。

先只考虑第一种朋友关系,即亦或后二进制尾位为0。我们发现,同奇偶的一定有边,那么其补图上,同奇偶的则必定无边。这满足二分图的性质,于是我们考虑将同奇偶的分在二分图的同侧。

再考虑第二种朋友关系,用这种关系在二分图的两部之间连边。

二分图中,最大独立集=顶点数-最大匹配数,那么对于每一次枚举,我们都可以用匈牙利算法算出答案。


【代码】

/***********************
    ID:Ciocio
	LANG:C++
	DATE:2014-1-18
	TASK:friend circle
************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

#define MAXN 205
#define MAXM 3005
#define MAXE 500005
#define p_b push_back
#define sz size

int N,M,E,ans,tot;
int A[MAXN],B[MAXM];
int Next[MAXE],Last[MAXM],Y[MAXE],Link[MAXM];
bool Mat[MAXN][MAXM],road[MAXM];
vector <int> Node;

void _read(int &x)
{
	char tt=getchar();
	while(tt<'0'||'9'<tt) tt=getchar();
	for(x=0;'0'<=tt&&tt<='9';x=(x<<1)+(x<<3)+tt-'0',tt=getchar());
}

void _init()
{
	_read(N);_read(M);_read(E);
	for(int i=1;i<=N;i++) _read(A[i]);
	for(int i=1;i<=M;i++) _read(B[i]);
	int a,b;
	for(int i=1;i<=E;i++)
	{
		_read(a);_read(b);
		Mat[a][b]=true;
	}
}

int _bitcnt(int x)
{
	int rt=0;
	while(x) rt^=(x&1),x>>=1;
	return rt;
}

bool _find(int v)
{
	for(int j=Last[v],i=Y[j];j;j=Next[j],i=Y[j])
		if(!road[i])
		{
			road[i]=true;
			if(Link[i]==0||_find(Link[i]))
			{
				Link[i]=v;
				return true;
			}
		}
	return false;
}

void _addedge(int a,int b){Y[++tot]=b;Next[tot]=Last[a];Last[a]=tot;}

void _All_clear()
{
	tot=0;Node.clear();
	memset(Link,0,sizeof Link);
	memset(Last,0,sizeof Last);
}

void _solve()
{
	int temp;
	//no person from country A
	temp=M;
	_All_clear();
	for(int i=1;i<=M;i++)
		if(!(B[i]&1))
			for(int j=1;j<=M;j++)
				if(B[j]&1)
					if(_bitcnt(B[i]|B[j])==0)     //由于是建立补图,连边的原则改变
						_addedge(i,j);
	for(int i=1;i<=M;i++)
		if(!(B[i]&1))
		{
			memset(road,0,sizeof road);
			if(_find(i)) temp--;
		}
	ans=max(ans,temp);
	//1 person from country A
	for(int k=1;k<=N;k++)
	{
		temp=1;
		_All_clear();
		for(int i=1;i<=M;i++) 
			if(Mat[k][i])
			{
				Node.p_b(i);             //用向量Node记录关联的B中的人
				temp++;
			}
		int m=Node.sz();
		for(int i=0;i<m;i++)
			if(!(B[Node[i]]&1))
				for(int j=0;j<m;j++)
					if(B[Node[j]]&1)
						if(_bitcnt(B[Node[i]]|B[Node[j]])==0)
							_addedge(Node[i],Node[j]);
		for(int i=0;i<m;i++)
			if(!(B[Node[i]]&1))
			{
				memset(road,0,sizeof road);
				if(_find(Node[i])) 
				{
					temp--;
					if(temp<=ans) break;
				}
		    }
		ans=max(ans,temp);
	}
	//2 persons from country A
	for(int p=1;p<=N;p++)
		for(int k=p+1;k<=N;k++)
			if((A[p]^A[k])&1)
			{
				temp=2;
				_All_clear();
				for(int i=1;i<=M;i++)
					if(Mat[p][i]&&Mat[k][i])
					{
						Node.p_b(i);
						temp++;
					}
				int m=Node.sz();
				for(int i=0;i<m;i++)
					if(!(B[Node[i]]&1))
						for(int j=0;j<m;j++)
							if(B[Node[j]]&1)
								if(_bitcnt(B[Node[i]]|B[Node[j]])==0)
									_addedge(Node[i],Node[j]);
				for(int i=0;i<m;i++)
					if(!(B[Node[i]]&1))
					{
						memset(road,0,sizeof road);
						if(_find(Node[i]))
						{
							temp--;
							if(temp<=ans) break;
						}
					}
				ans=max(ans,temp);
			}
	cout<<ans<<endl;
}

int main()
{
	int Case;_read(Case);
	while(Case--)
	{
		ans=0;
		memset(Mat,0,sizeof Mat);
	    _init();
	    _solve();
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值