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;
}