数位dp;
int dp[][][]... , digit[](存每一位上的最大值);
int dfs(int per, int state, int zero, bool limit){
if(pre==-1) return 1;
if(!limit && dp[pre][state]!=-1) return dp[pre][state];
int up=limit?digit[pre]:9;
int ans=0;
for(int i=0; i<=up; i++){
if(...) ...;
if(...) ...;
ans+=dfs(pre-1, new_state, zero&&i==0, limit&&i==up);
}
if(!limit) dp[pre][state]=ans;
return ans;
}
int solve(int x){
int pre=0;
while(x){
digit[pre++]=x%10;
x/=10;
}
return dfs(pre-1, stete(初始状态), 1, 1);
}
直线分割, 第n条线与前n-1条线最多有n-1个交点, 那么就会多出(n-1) + 1部分;
折线分割, 第n条线与前n-1条线最多有4*(n-1)个交点, 那么就会多出4*(n-1)+1部分;
直线分割公式:S=n*(n+1)/2+1;
折线分割公式:S=(2*n-1)*n+1;
S表示被分割成几部分;
最长上升子序列:
O(n^2)
const int maxn=100020;
const int inf=0x3f3f3f3f;
int dp[maxn];//以a[i]为结尾的最长自增子序列长度
int a[maxn];
int n;
int LIS(int a[],int n)//最长上升子序列
{
int m;
dp[0]=1;
for(int i=1;i<n;i++)
{
m=0;
for(int j=1;j<i;j++)
{
if(dp[j]>m&&a[j]<a[i])
m=dp[j];
}
dp[i]=m+1;
}
return dp[n-1];
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
cout<<LIS(a,n)<<endl;
}
return 0;
}
int a[maxn];
int b[maxn];
int n;
int bin_search(int aim,int low,int high)
{
while(low<=high)
{
int mid=(low+high)/2;
if(aim>b[mid])
low=mid+1;
else
high=mid-1;
}
return low;
}
O(nlogn),二分(手写)
int LIS(int a[],int n)
{
int len=0,p
os;
for(int i=1;i<=n;i++)
{
if(a[i]>b[len]||len==0)
{
len++;
b[len]=a[i];
}
else
{
pos=bin_search(a[i],0,len);
b[pos]=a[i];
}
}
return len;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
cout<<LIS(a,n)<<endl;
}
return 0;
}
O(nlogn), 二分(lower_bound())
const int maxn=100020;
const int inf=0x3f3f3f3f;
int dp[maxn];
int a[maxn];
int n;
int LIS(int a[],int n)
{
//fill(dp,dp+n,0x7fffffff);
memset(dp,inf,sizeof(dp));
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
*lower_bound(dp,dp+n,a[i])=a[i];
}
int len=lower_bound(dp,dp+n,inf)-dp;
return len;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
cout<<LIS(a,n)<<endl;
}
return 0;
}
最长公共子序列O(nlogn)
void solve() {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (s1[i] == s2[j]) {
dp[i + 1][j + 1] = dp[i][j] + 1;
}else {
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);
} } }
}
第k优解问题:
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
int dp[1100][40], val[110], vol[110], a[40], b[40];
int main(){
int T;
scanf("%d", &T);
while(T--){
int N, V, K;
scanf("%d%d%d", &N, &V, &K);
for(int i=1; i<=N; i++){
scanf("%d", &val[i]);
}
for(int i=1; i<=N; i++){
scanf("%d", &vol[i]);
}
memset(dp, 0, sizeof(dp));
for(int i=1; i<=N; i++){
for(int j=V; j>=vol[i]; j--){
for(int k=1; k<=K; k++){
a[k]=dp[j][k];
b[k]=dp[j-vol[i]][k]+val[i];
}
int k, x, y;
x=y=k=1;
a[K+1]=b[K+1]=-1;
while(k!=K+1&&(a[x]!=-1||b[x]!=-1)){
if(a[x]>=b[y]) dp[j][k]=a[x++];
else dp[j][k]=b[y++];
if(dp[j][k]!=dp[j][k-1]) k++;
}
}
}
printf("%d\n", dp[V][K]);
}
return 0;
}
有限制条件的递推:
题意:选出N个士兵编号1~N, 一共三种士兵, G,R,P三种, 士兵总数是无限大;G士兵至少有M个连续地, R士兵至多有K个连续地, P士兵随便放没有限制;
现在求这么一个事件Q:至少M个连续地士兵G&&至多K个连续的士兵R;
可以看出两个限制条件是不统一的, 一个至多, 一个至少,现在我们将他的限制条件统一;
设事件A为:至多N个连续地士兵G&&至多K个连续地士兵R;
设事件B为:至多M-1个连续地士兵G&&至多K个连续地士兵R;
那么可知Q=A-B;所以我们只需分别求出A,B的数量即可间接地求出Q;
A, B是相通的, 均为至多X个连续地士兵G&&至多Y个连续地士兵R;求解方式是相同的;
数组dp[i][j]表示第i个位置放的是j士兵(j==0:G; j==1:R; j==2:P;);
sum=dp[i-1][0]+dp[i-1][1]+dp[i-1][2];(sum表示前i-1个位置有几种情况);
对于P士兵, 由于是随便放, 没有限制条件, 所以在i位置可以放P而不需要考虑非法情况;
所以 dp[i][2]=sum;
对于G士兵,:
当i <= X: 此时前边最多X-1个G, 第i个位置可以放G, 且不会有其他非法情况;
dp[i][0]=sum;
当i == X+1: 只有当1~i-1全为G, 即G有连续X个时, i位置不能放G, 其他情况均可;
dp[i][0]=sum-1;
当i > X+1: 当i-X~i-1全为G, 既有连续X个G, 位置不能放G, 此时i-X-1位置一定为P或R
dp[i][0]=sum-dp[i-X-1][1]-dp[i-X-1][2];
现在G士兵已放完, 下面放R士兵, 其实道理与G相同, 把X换成Y;
对于R士兵:
当i <= Y: 此时前边最多Y-1个R, 第i个位置可以放R, 且不会有其他非法情况;
dp[i][1]=sum;
当i == Y+1: 只有当1~i-1全为R, 即R有连续Y个时, i位置不能放R, 其他情况均可;
dp[i][1]=sum-1;
当i > Y+1: 当i-Y~i-1全为R, 既有连续Y个R, 位置不能放R, 此时i-Y-1位置一定为P或G
dp[i][1]=sum-dp[i-Y-1][0]-dp[i-Y-1][2];
· #include <iostream>
· #include <algorithm>
· #include <string.h>
· #include <stdio.h>
· using namespace std;
· int N, M, K;
· const long long mod = 1e9+7;
· const int maxn = 1e6+10;
· long long dp[maxn][5];
· long long ans(int m, int k){
· long long sum=0;
· dp[0][0]=1;
· dp[0][1]=dp[0][2]=0;
· for(int i=1; i<=N; i++){
· sum=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%mod;
· //i处放P士兵;
· dp[i][2]=sum;
· //i处放G士兵;
· if(i <= m) dp[i][0]=sum;
· if(i == m+1) dp[i][0]=(sum-1+mod)%mod;
· if(i > m+1) dp[i][0]=(sum-dp[i-m-1][1]-dp[i-m-1][2]+mod)%mod;
· //i处放R士兵;
· if(i <= k) dp[i][1]=sum;
· if(i == k+1) dp[i][1]=(sum-1+mod)%mod;
· if(i > k+1) dp[i][1]=(sum-dp[i-k-1][0]-dp[i-k-1][2]+mod)%mod;
· }
· return (dp[N][0]+dp[N][1]+dp[N][2])%mod;
· }
· int main(){
· while(~scanf("%d%d%d", &N, &M, &K)){
· printf("%lld\n", ((ans(N, K)-ans(M-1, K))%mod+mod)%mod);
· }
· return 0;
· }
大数乘法:
void high_precisionMultiplication(string a, string b)//a, b表示两个因子;
{
vector<int> factor_a, factor_b, product;
int len_a=a.size(), len_b=b.size();
int i=0, j;
/***将两个因子转化成int数组, 这里用vector***/
while(i<len_a){
factor_a.push_back(a[i++]-'0');
}
reverse(factor_a.begin(), factor_a.end());
i=0;
while(i<len_b){
factor_b.push_back(b[i++]-'0');
}
reverse(factor_b.begin(), factor_b.end());
/*******************************************/
/***初始化***/
for(i=0; i<len_a+len_b; i++)
product.push_back(0);
for(i=0; i<len_a; i++){
for(j=0; j<len_b; j++){
product[i+j]+=factor_a[i]*factor_b[j];
}
}
int tem=0, tt;
for(i=0; i<len_a+len_b; i++){
tt=(product[i]+tem)/10;
product[i]=(product[i]+tem)%10;
tem=tt;
}
/***除去前导零***/
for(i=len_a+len_b-1; i>0; i--)
if(product[i]==0) product.pop_back();
else break;
reverse(product.begin(), product.end());
int len=product.size();
/***输出乘积***/
for(i=0; i<len; i++)
cout << product[i];
cout << endl;
}
归并排序逆序数(O(nlogn));
· int Mergesort(vector<int> &a, int left, int right, int mid){
· vector<int> b;
· int i, j, k, cnt;
· i=left, j=mid+1, k=0, cnt=0;
· while(i<=mid&&j<=right){
· if(a[i]<=a[j]) b.push_back(a[i++]);
· else b.push_back(a[j++]), cnt+=mid-i+1;//i~mid和mid+1~j已经是顺序的了, 所以如果a[i]>a[j]则i~mid都>a[j], 此时一共mid-i+1个逆序数
· }
· while(i<=mid) b.push_back(a[i++]);
· while(j<=right) b.push_back(a[j++]);
· for(i=left, j=0; i<=right; i++, j++){
· a[i]=b[j];
· }
· return cnt;
· }
· int cnt;
· int Merge(vector<int> &a, int left, int right){
· if(left<right){
· int mid=(left+right)/2;
· Merge(a, left, mid);
· Merge(a, mid+1, right);
· cnt+=Mergesort(a, left, right, mid);
· }
· return cnt;
· }
矩阵快速幂:
node mul(node a, node b){
node c;
c.x=a.x;
c.y=b.y;
memset(c.z, 0, sizeof(c.z));
for(int i=0; i<c.x; i++){
for(int j=0; j<c.y; j++){
for(int k=0; k<b.y; k++)
c.z[i][j]+=a.z[i][k]*b.z[k][j];
}
}
return c;
}
node power(node a, int n){
node b;
b.x=a.x;
b.y=a.y;
for(int i=0; i<a.x; i++){
b.z[i][i]=1;
}
b.z[0][1]=b.z[1][0]=0;
while(n>0){
if(n&1) b=mul(b, a);
a=mul(a, a);
n>>=1;
}
return b;
}
约瑟夫环:
n个人站成一圈, 编号为1-n, 开始报数, 报到q的淘汰, 求最后的赢家;
对于n, 与 q, ans=(ans+q)%i(i:2~n循环);q表示报数为q的淘汰, i表示本局有多少人(n),
ans表示上局获胜的人的下标(下标由0-(n-1)编号)(ans初始化为0);
KMP
string s;
int nxt[100000];
//计算nxt数组;
//时间复杂度O(n)
void cal_next(string s){
nxt[0]=-1;
int k=-1, j=0, len=s.size();
while(j<len){
if(k==-1||s[k]==s[j]){
k++;
j++;
nxt[j]=k;
}
else k=nxt[k];
}
}
//将str与text匹配;
//时间复杂度O(n+m)
int KMP(string text, string str) {
//text表示原串, str表示待匹配串;
cal_next(str);//求带匹配串的next数组;
int n = text.size();
int m = str.size();
int j = 0;
for (int i = 0; i < n; i++) {
while (j && text[i] != str[j])
j = nxt[j];
if (str[j] == text[i])
j++;
if (j == m)
return 1;
}
return 0;
}
//最小字符串;
//时间复杂度O(n)
void get_minString(string s){
int len=s.size();
int i=0, j=1, k=0;
while(i<len&&j<len&&k<len){
int t=s[(i+k)%len]-s[(j+k)%len];
if(t==0) k++;
else{
if(t>0) i+=k+1;
else j+=k+1;
if(i==j) j++;
k=0;
}
}
int id=min(i, j);//id表示最小位置的起点;
string tem;
tem=s.substr(id, len-id)+s.substr(0, id);
cout << "min: " << tem << endl;
return;
}
//最大字符串;
//时间复杂度O(n)
void get_maxString(string s){
int len=s.size();
int i=0, j=1, k=0;
while(i<len&&j<len&&k<len){
int t=s[(i+k)%len]-s[(j+k)%len];
if(t==0) k++;
else{
if(t>0) j+=k+1;
else i+=k+1;
if(i==j) j++;
k=0;
}
}
int id=min(i, j);//id表示最大位置的起点;
string tem;
tem=s.substr(id, len-id)+s.substr(0, id);
cout << "max: " << tem << endl;
return;
}
马拉车算法, 计算回文串;
//初始化字符串;
string get_copy(string s){
string t="$";
int len=s.size();
int i=0;
while(i<len){
t+="#";
t+=s[i++];
}
t+="#";
return t;
}
int p[2000005];//p[i]-1表示以i位置字符为中心的最长回文串的长度; i/2是i字符在原串中的位置;
//返回最长回文串长度;
//时间复杂度O(n)
int Manacher(string s){
cout << s << endl;
int maxx=0, id=0, ans=0;
int len=s.size();
for(int i=0; i<len; i++){
p[i]=maxx>i?min(p[2*id-i], maxx-i):1;
while(s[p[i]+i]==s[i-p[i]]) p[i]++;
if(i+p[i]>maxx){
maxx=p[i]+i;
id=i;
}
ans=max(p[i]-1, ans);
}
for(int i=0; i<len; i++)
cout << p[i];
cout << endl;
return ans;
}
扩展KMP
//拓展kmp是对KMP算法的扩展,它解决如下问题:
//定义母串S,和字串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,
//也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有//extend[i](0<=i<n);
//时间复杂度分析
//下面来分析一下算法的时间复杂度,通过上面的算法介绍可以知道,
//对于第一种情况,无需做任何匹配即可计算出extend[i],
//对于第二种情况,都是从未被匹配的位置开始匹配,匹配过的位置不再匹配,
//也就是说对于母串的每一个位置,都只匹配了一次,
//所以算法总体时间复杂度是O(n)的,同时为了计算辅助数组next[i]需要先对字串T进行一次//拓展kmp算法处理,
//所以拓展kmp算法的总体复杂度为O(n+m)的。其中n为母串的长度,m为子串的长度。
const int maxn=1e6+10; //字符串长度最大值
int nxt[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算nxt数组
void GETnxt(char *str, int len)
{
int i=0,j,po;
nxt[0]=len;//初始化nxt[0]
while(str[i]==str[i+1]&&i+1<len)//计算nxt[1]
i++;
nxt[1]=i;
po=1;//初始化po的位置
for(i=2; i<len; i++)
{
if(nxt[i-po]+i<nxt[po]+po)//第一种情况,可以直接得到nxt[i]的值
nxt[i]=nxt[i-po];
else//第二种情况,要继续匹配才能得到nxt[i]的值
{
j=nxt[po]+po-i;
if(j<0)j=0;//如果i>po+nxt[po],则要从头开始匹配
while(i+j<len&&str[j]==str[j+i])//计算nxt[i]
j++;
nxt[i]=j;
po=i;//更新po的位置
}
}
}
//计算extend数组
void EXKMP(char *s1,char *s2, int len, int l2)
{
int i=0,j,po;
GETnxt(s2, l2);//计算子串的nxt数组
while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
i++;
ex[0]=i;
po=0;//初始化po的位置
for(i=1; i<len; i++)
{
if(nxt[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
ex[i]=nxt[i-po];
else//第二种情况,要继续匹配才能得到ex[i]的值
{
j=ex[po]+po-i;
if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
j++;
ex[i]=j;
po=i;//更新po的位置
}
}
}
后缀数组
int c[10010];//c[sort key]=the number of the strings which have this key(桶);
int x[10010];//x[string's NUM]=the rank with the sort key now;
int y[10010];//y[the rank of the second key]=string's NUM;
char s[10010];//s[the adress in the string]=the char;
int sa[10010];//sa[the rank of the string]=num;
int rank[10010];//rank[the string's num]=rank;
int height[10010];//height[rank]=len of lcp
int n,m;//n:the len of the string,m:the diction_num
void build_SA();
void build_rank();
void build_height();
void build_SA(){
int i,j,k;
for(i=1;i<=m;i++)c[i]=0;
//memset;
for(i=0;i<n;i++)x[i]=s[i];
//make x when the sort key is only one char;
for(i=0;i<n;i++)c[x[i]]++;
//sort;
for(i=2;i<=m;i++)c[i]+=c[i-1];
//Prefix sum to find rank;
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
//make sa with the first char in each string;
//sort with the first char;
for(k=1;k<=n;k<<=1){
int num=0;
for(i=n-k;i<n;i++)y[num++]=i;
//get the string which don't have the NO.k char;
for(i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
//use the stringB which has the head char that is the No.k char in the stringA to sort stringA;
//sort with the second key;
for(i=1;i<=m;i++)c[i]=0;
//memset;
for(i=0;i<=n;i++)c[x[i]]++;
//sort with the last total sort key which could be the first key now;
for(i=2;i<=m;i++)c[i]+=c[i-1];
//Prefix sum to find rank;
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i],y[i]=0;
//make sa with first K char;
//sort with the first key and as it finish the work, sorting with first K char, is finished;
swap(x,y);
//as y is no use now, we can use it to help change x;
num=1;x[sa[0]]=1;
for(i=1;i<n;i++)
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k])
x[sa[i]]=++num;
else
x[sa[i]]=num;
//if the string with sa[i] has the content which isn't the same as the content of the string with sa[i-1];
//x[sa[i]] is diferent from x[sa[i-1]];
if(num>=n)break;
//it means the work is finished that the key has the same number as the string;
m=num;
//expand the ton;
//make the x with the total key now as the first key in the next step;
}
}
void build_rank(){
for(int i=0;i<n;i++)rank[sa[i]]=i;
//rank[sa[i]]=i;
}
void build_height(){
int i,j,k=0;
for(i=0;i<n;i++){
if(!rank[i])continue;
if(k)k--;
//height[rank[i]]>=height[rank[i-1]]-1;
j=sa[rank[i]-1];
while(j+k<n&&i+k<n&&s[j+k]==s[i+k])k++;
//height[rank[i]]means the len of the lcp of the rank[i] and the rank[i]-1
height[rank[i]]=k;
}
}
简洁完整模板:
#include<cstdio>
#include<algorithm>
using namespace std;
int c[10010];
int x[10010];
int y[10010];
char s[10010];
int sa[10010];
int rank[10010];
int height[10010];
int n,m;
void build_SA();
void build_rank();
void build_height();
int main()
{
int i,j,k;
scanf("%d%d",&n,&m);
scanf("%s",s);
build_SA();
for(i=0;i<n;i++){
for(j=sa[i];j<n;j++)
printf("%c",s[j]);
printf("\n");
}
build_rank();
build_height();
}
void build_SA(){
int i,j,k;
for(i=1;i<=m;i++)c[i]=0;
for(i=0;i<n;i++)x[i]=s[i];
for(i=0;i<n;i++)c[x[i]]++;
for(i=2;i<=m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(k=1;k<=n;k<<=1){
int num=0;
for(i=n-k;i<n;i++)y[num++]=i;
for(i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
for(i=1;i<=m;i++)c[i]=0;
for(i=0;i<=n;i++)c[x[i]]++;
for(i=2;i<=m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i],y[i]=0;
swap(x,y);
num=1;x[sa[0]]=1;
for(i=1;i<n;i++)
if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k])
x[sa[i]]=++num;
else
x[sa[i]]=num;
if(num>=n)break;
m=num;
}
}
void build_rank(){
for(int i=0;i<n;i++)rank[sa[i]]=i;
}
void build_height(){
int i,j,k=0;
for(i=0;i<n;i++){
if(!rank[i])continue;
if(k)k--;
j=sa[rank[i]-1];
while(j+k<n&&i+k<n&&s[j+k]==s[i+k])k++;
height[rank[i]]=k;
}
}
字典树:
问题1:给定一些字符串建立字典树,然后再给出一个字符串,问该字符串是前面多少个字符串的前缀。
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
struct Trie
{
int cnt;//有多少单词经过该节点
Trie*next[27];
Trie()
{
cnt=0;
for(int i=0;i<27;i++)
next[i]=NULL;
}
}root;
void create(char *s)//将字符串s建立在trie树中
{
Trie *p=&root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int id=s[i]-'a';//唯一标识
if(p->next[id]==NULL)
{
p->next[id]=new Trie;
p->next[id]->cnt++;
}
else
p->next[id]->cnt++;
p=p->next[id];
}
}
int find(char *s)//查找字符串s是多少单词的前缀。
{
Trie *p=&root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(p->next[id]==NULL)
return 0;
p=p->next[id];
}
return p->cnt;
}
int main()
{
char s[11];
while(gets(s)&&s[0]!='\0')
create(s);//根据给定的字符串建立字典树
while(cin>>s)
cout<<find(s)<<endl;
return 0;
}
题目2:判断输入的字符串中是否存在某一个字符串是其他一个字符串的前缀。
#include <iostream>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
using namespace std;
const int maxn=10;
char str[12];
int t,n;
bool ok;
struct Trie
{
int flag;//当前串结束的标志,当前节点有单词经过,标志位1,在,哪个节点结束,其节点的flag=-1
Trie*next[10];
Trie()
{
flag=0;
}
}root;
/*错误的写法
void create(char *s)
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
p->next[id]=(Trie *)malloc(sizeof(root));
p->next[id]->flag=1;
}
else
p->next[id]->flag=1;
p=p->next[id];
}
p->flag=-1;//结束的标志
}*/
void create(char *s)
{
int len=strlen(s);
Trie *p=&root,*q;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
q=(Trie *)malloc(sizeof(root));
q->flag=1;
for(int j=0;j<10;j++)
q->next[j]=NULL;
p->next[id]=q;
p=p->next[id];
}
else
{
p->next[id]->flag=1;
p=p->next[id];
}
}
p->flag=-1;
}
int find(char *s)
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
return 0;//以前没有单词经过
if(p->next[id]->flag==-1)
return -1;//以前串是当前串的前缀
p=p->next[id];
}
return -1;//当前串是以前串的前缀
}
void release(Trie *root)//释放空间
{
if(root==NULL)
return ;
for(int i=0;i<10;i++)
{
if(root->next[i]!=NULL)
release(root->next[i]);
}
free(root);
}
int main()
{
scanf("%d",&t);
while(t--)
{
ok=1;
for(int i=0;i<10;i++)
root.next[i]=NULL;
scanf("%d",&n);
while(n--)
{
scanf("%s",str);
if(find(str)==-1)
ok=0;
if(!ok)//有前缀,后面的就不用建树了
continue;
create(str);
}
if(ok)
printf("YES\n");
else
printf("NO\n");//存在前缀输出NO
release(&root);
}
return 0;
}
题目3:将输入的字符串利用trie数进行编号 ,从1开始.
int cnt=1;
struct Trie
{
bool ok;
int id;
Trie *next[27];
Trie()
{
ok=0;
for(int i=0;i<=27;i++)
next[i]=NULL;
}
}root;
int hash(char *s) //返回字符串s的编号,创建和查找于一身
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(!p->next[id])
p->next[id]=new Trie;
p=p->next[id];
}
if(p->ok)
return p->id;
else
{
p->ok=1;
p->id=cnt++;
}
return p->id;
}
AC自动机
AC自动机:Aho-Corasickautomation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。
一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。
要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识。
AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程。
简单来说,AC自动机是用来进行多模式匹配(单个主串,多个模式串)的高效算法。
AC自动机的构造过程
使用Aho-Corasick算法需要三步:
建立模式串的Trie
给Trie添加失败路径
根据AC自动机,搜索待处理的文本
时间复杂度
假设有N个模式串,平均长度为L;文章长度为M。
建立Trie树:O(N*L)
建立fail指针:O(N*L)
模式匹配:O(M*L) (注:之所以要乘以一个L,是因为在统计的时候需要顺着链回溯到root结点)
所以,总时间复杂度为:O( (N+M)*L )
例题模板:
题意:第一行输入测试数据的组数,然后输入一个整数n,接下来的n行每行输入一个单词,
最后输入一个字符串,问在这个字符串中有多少个单词出现过。
解题思路:这是一道多模式串的字符匹配问题,又名AC自动机,听着名字好高大上的赶脚。
KMP处理的是单模式串匹配,其实KMP也能处理多模式串匹配的问题,不过当数据很大的时候,
是相当的耗费时间,所以就有前辈们发明了AC自动机这个高效的多模式串匹配算法。
具体代码;
-----------------------------------------------------------
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 5;
const int MAX = 10000000;
int cnt;
struct node{
node *next[26];
node *fail;
int sum;
};
node *root;
char key[70];
node *q[MAX];
int head,tail;
node *newnode;
char pattern[maxn];
int N;
void Insert(char *s)
{
node *p = root;
for(int i = 0; s[i]; i++)
{
int x = s[i] - 'a';
if(p->next[x] == NULL)
{
newnode=(struct node *)malloc(sizeof(struct node));
for(int j=0;j<26;j++) newnode->next[j] = 0;
newnode->sum = 0;newnode->fail = 0;
p->next[x]=newnode;
}
p = p->next[x];
}
p->sum++;
}
void build_fail_pointer()
{
head = 0;
tail = 1;
q[head] = root;
node *p;
node *temp;
while(head < tail)
{
temp = q[head++];
for(int i = 0; i <= 25; i++)
{
if(temp->next[i])
{
if(temp == root)
{
temp->next[i]->fail = root;
}
else
{
p = temp->fail;
while(p)
{
if(p->next[i])
{
temp->next[i]->fail = p->next[i];
break;
}
p = p->fail;
}
if(p == NULL) temp->next[i]->fail = root;
}
q[tail++] = temp->next[i];
}
}
}
}
void ac_automation(char *ch)
{
node *p = root;
int len = strlen(ch);
for(int i = 0; i < len; i++)
{
int x = ch[i] - 'a';
while(!p->next[x] && p != root) p = p->fail;
p = p->next[x];
if(!p) p = root;
node *temp = p;
while(temp != root)
{
if(temp->sum >= 0)
{
cnt += temp->sum;
temp->sum = -1;
}
else break;
temp = temp->fail;
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
root=(struct node *)malloc(sizeof(struct node));
for(int j=0;j<26;j++) root->next[j] = 0;
root->fail = 0;
root->sum = 0;
scanf("%d",&N);
getchar();
for(int i = 1; i <= N; i++)
{
scanf("%s", key);
Insert(key);
}
scanf("%s", pattern);
cnt = 0;
build_fail_pointer();
ac_automation(pattern);
printf("%d\n",cnt);
}
return 0;
}
-----------------------------------------------------------------
AC自动机:不用指针的模板;
N个病毒编码,M个网站编码,问每个网站编码中包含那几种病毒;
#include <bits/stdc++.h>
using namespace std;
int n, m;
struct Trie{
int next[100010][128], fail[100010], end[100010];
int root, L;
int new_node(){
for(int i=0; i<128; i++){
next[L][i]=-1;
}
fail[L]=-1;
end[L++]=-1;
return L-1;
}
void init(){
L=0;
root=new_node();
}
void insert(char *s, int id){
int p=root, q;
int len=strlen(s);
int x;
for(int i=0; i<len; i++){
x=s[i];
if(next[p][x]==-1){
q=new_node();
next[p][x]=q;
}
p=next[p][x];
}
end[p]=id;
}
void define_fail(){
queue<int> que;
fail[root]=root;
for(int i=0; i<128; i++){
if(next[root][i]==-1) next[root][i]=root;
else{
fail[next[root][i]]=root;
que.push(next[root][i]);
}
}
while(!que.empty()){
int p=que.front();
que.pop();
for(int i=0; i<128; i++){
if(next[p][i]==-1) next[p][i]=next[fail[p]][i];
else{
fail[next[p][i]]=next[fail[p]][i];
que.push(next[p][i]);
}
}
}
}
bool vis[510];
bool query(char *s, int id){
int len=strlen(s);
int p=root;
memset(vis, false, sizeof(vis));
bool flag=false;
int x;
for(int i=0; i<len; i++){
x=s[i];
p=next[p][x];
int temp=p;
while(temp!=root){
if(end[temp]!=-1){
vis[end[temp]]=true;
flag=true;
}
temp=fail[temp];
}
}
if(!flag) return false;
printf("web %d:", id);
for(int i=1; i<=n; i++){
if(vis[i]) printf(" %d", i);
}
puts("");
return true;
}
};
char vir[210], web[10010];
Trie ac;
int main(){
scanf("%d", &n);
ac.init();
for(int i=1; i<=n; i++){
scanf("%s", vir);
ac.insert(vir, i);
}
ac.define_fail();
int tot=0;
scanf("%d", &m);
for(int i=1; i<=m; i++){
scanf("%s", web);
if(ac.query(web, i)) tot++;
}
printf("total: %d\n", tot);
return 0;
}
----------------------------------------------------------------------------------------------------------------------
线段树:
点更新
struct node
{
int left, right;
int max, sum;
};
node tree[maxn << 2];
int a[maxn];
int n;
int k = 1;
int p, q;
string str;
void build(int m, int l, int r)//m 是 树的标号
{
tree[m].left = l;
tree[m].right = r;
if (l == r){
tree[m].max = a[l];
tree[m].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(m << 1, l, mid);
build(m << 1 | 1, mid + 1, r);
tree[m].max = max(tree[m << 1].max, tree[m << 1 | 1].max);
tree[m].sum = tree[m << 1].sum + tree[m << 1 | 1].sum;
}
void update(int m, int a, int val)//a 是 节点位置, val 是 更新的值(加减的值)
{
if (tree[m].left == a && tree[m].right == a){
tree[m].max += val;
tree[m].sum += val;
return;
}
int mid = (tree[m].left + tree[m].right) >> 1;
if (a <= mid){
update(m << 1, a, val);
}
else{
update(m << 1 | 1, a, val);
}
tree[m].max = max(tree[m << 1].max, tree[m << 1 | 1].max);
tree[m].sum = tree[m << 1].sum + tree[m << 1 | 1].sum;
}
int querySum(int m, int l, int r)
{
if (l == tree[m].left && r == tree[m].right){
return tree[m].sum;
}
int mid = (tree[m].left + tree[m].right) >> 1;
if (r <= mid){
return querySum(m << 1, l, r);
}
else if (l > mid){
return querySum(m << 1 | 1, l, r);
}
return querySum(m << 1, l, mid) + querySum(m << 1 | 1, mid + 1, r);
}
int queryMax(int m, int l, int r)
{
if (l == tree[m].left && r == tree[m].right){
return tree[m].max;
}
int mid = (tree[m].left + tree[m].right) >> 1;
if (r <= mid){
return queryMax(m << 1, l, r);
}
else if (l > mid){
return queryMax(m << 1 | 1, l, r);
}
return max(queryMax(m << 1, l, mid), queryMax(m << 1 | 1, mid + 1, r));
}
build(1,1,n);
update(1,a,b);
query(1,a,b);
区间更新
typedef long long ll;
const int maxn = 100010;
int t,n,q;
ll anssum;
struct node{
ll l,r;
ll addv,sum;
}tree[maxn<<2];
void maintain(int id) {
if(tree[id].l >= tree[id].r)
return ;
tree[id].sum = tree[id<<1].sum + tree[id<<1|1].sum;
}
void pushdown(int id) {
if(tree[id].l >= tree[id].r)
return ;
if(tree[id].addv){
int tmp = tree[id].addv;
tree[id<<1].addv += tmp;
tree[id<<1|1].addv += tmp;
tree[id<<1].sum += (tree[id<<1].r - tree[id<<1].l + 1)*tmp;
tree[id<<1|1].sum += (tree[id<<1|1].r - tree[id<<1|1].l + 1)*tmp;
tree[id].addv = 0;
}
}
void build(int id,ll l,ll r) {
tree[id].l = l;
tree[id].r = r;
tree[id].addv = 0;
tree[id].sum = 0;
if(l==r) {
tree[id].sum = 0;
return ;
}
ll mid = (l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
maintain(id);
}
void updateAdd(int id,ll l,ll r,ll val) {
if(tree[id].l >= l && tree[id].r <= r)
{
tree[id].addv += val;
tree[id].sum += (tree[id].r - tree[id].l+1)*val;
return ;
}
pushdown(id);
ll mid = (tree[id].l+tree[id].r)>>1;
if(l <= mid)
updateAdd(id<<1,l,r,val);
if(mid < r)
updateAdd(id<<1|1,l,r,val);
maintain(id);
}
void query(int id,ll l,ll r) {
if(tree[id].l >= l && tree[id].r <= r){
anssum += tree[id].sum;
return ;
}
pushdown(id);
ll mid = (tree[id].l + tree[id].r)>>1;
if(l <= mid)
query(id<<1,l,r);
if(mid < r)
query(id<<1|1,l,r);
maintain(id);
}
int main() {
scanf("%d",&t);
int kase = 0 ;
while(t--){
scanf("%d %d",&n,&q);
build(1,1,n);
int id;
ll x,y;
ll val;
printf("Case %d:\n",++kase);
while(q--){
scanf("%d",&id);
if(id==0){
scanf("%lld %lld %lld",&x,&y,&val);
updateAdd(1,x+1,y+1,val);
}
else{
scanf("%lld %lld",&x,&y);
anssum = 0;
query(1,x+1,y+1);
printf("%lld\n",anssum);
} } }
return 0;
}
//区间更新例题模板:
N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#define MAX 100000+5
using namespace std;
int segtree[MAX<<2]; //树结点,用来储存气球的粉刷次数,由于此题只与叶结点有关,所以中间节点的值不需要关心;
int lazy[MAX<<2]; //懒惰标记;
int N; //气球个数;
void build(int node, int left, int right){//建立线段树,叶结点全部为零;
if(left==right){
segtree[node]=0;
return;
}
int mid=(left+right)>>1;
build(node<<1, left, mid);
build(node<<1|1, mid+1, right);
}
void pushdown(int node, int left, int right){//如果该层被标记,那么对他的左右孩子更新;
if(left!=right){//这句判断一定要加,不然会RE,血淋淋得教训啊;
segtree[node<<1]+=lazy[node];//更新左孩子;
segtree[node<<1|1]+=lazy[node];//更新右孩子;
lazy[node<<1|1]+=lazy[node]; //把标记传入下一层;
lazy[node<<1]+=lazy[node];
}
lazy[node]=0; //消除标记;
}
void update(int node, int left, int right, int l, int r, int add){//区间更新;传参:树根,区间范围,需要更新的区间,每个数要增加的值;
if(lazy[node]) pushdown(node, left, right);//先判断该层是否被标记,如果被标记就更新一次;
if(l>right || r<left) return;
if(l<=left && r>=right){
lazy[node]+=add; //找到更新区间后标记该层,表示该层以下需要被更新,需要增加的值为lazy内的值;
segtree[node]+=lazy[node]; //更新该层;
return;
}
int mid=(left+right)>>1;
update(node<<1, left, mid, l, r, add);
update(node<<1|1, mid+1, right, l, r, add);
}
void query(int node, int left, int right){
if(lazy[node]) pushdown(node, left, right);//不管查询还是更新每次都要先判断该层是否被更新;
if(left==right){ //if左右孩子相等,该点是叶结点,输出叶结点;
if(left==N) printf("%d\n",segtree[node]);
else printf("%d ",segtree[node]);
return;
}
int mid=(left+right)>>1;
query(node<<1, left, mid);
query(node<<1|1, mid+1, right);
}
int main(){
while(scanf("%d",&N),N!=0){
int a, b;
int i;
build(1, 1, N);
memset(lazy, 0, sizeof(lazy));
for(i=1; i<=N; i++){
scanf("%d%d",&a,&b);
update(1, 1, N, a, b, 1);
}
query(1, 1, N);
}
return 0;
}
树状数组:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 50005;
int a[maxn];
int n;
int lowbit(const int t) {
return t & (-t);
}
void insert(int t, int d) {
while (t <= n){
a[t] += d;
t = t + lowbit(t);
}
}
ll getSum(int t) {
ll sum = 0;
while (t > 0){
sum += a[t];
t = t - lowbit(t);
}
return sum;
}
int main() {
int t, k, d;
scanf("%d", &t);
k= 1;
while (t--){
memset(a, 0, sizeof(a));
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &d);
insert(i, d);
}
string str;
printf("Case %d:\n", k++);
while (cin >> str) {
if (str == "End") break;
int x, y;
scanf("%d %d", &x, &y);
if (str == "Query")
printf("%lld\n", getSum(y) - getSum(x - 1));
else if (str == "Add")
insert(x, y);
else if (str == "Sub")
insert(x, -y);
}
}
return 0;
}
题目分析
将一组数组a[N]
输入Query a b,输出SUM(ai + …… + aj)
输入Add i j,s[i] = s[i] + j
输入Sub i j,s[j] = s[i] - j
数组动态求和,明显的树状数组,调用树状数组模版:树状数组
一,每次修改的是一个点,所求的是关于某段区间;
#include <cstdio>
#include <cstring>
#define maxn 50047
int c[maxn], a[maxn];
int n,t;
int Lowbit(int x) // 2^k
{
return x&(-x);
}
void update(int i, int x)//i点增量为x
{
while(i <= n)
{
c[i] += x;
i += Lowbit(i);
}
}
int sum(int x)//区间求和 [1,x]
{
int sum=0;
while(x>0)
{
sum+=c[x];
x-=Lowbit(x);
}
return sum;
}
int Getsum(int x1,int x2) //求任意区间和
{
return sum(x2) - sum(x1-1);
}
int main()
{
int i , j;
scanf("%d",&t);
int count = 0;
while(t--)
{
count++;
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
scanf("%d",&n);
for(i = 1; i <= n; i++) //i须从1开始
{
scanf("%d",&a[i]);
update(i,a[i]); //初始的人数
}
printf("Case %d:\n",count);
char oper[11];
while(scanf("%s",oper)==1)
{
if(strcmp(oper,"End")==0)
break;
scanf("%d%d",&i,&j);
if(strcmp(oper,"Query")==0)
{
printf("%d\n",Getsum(i,j));
}
if(strcmp(oper,"Add")==0) //表示第i个营地增加j个人
{
a[i] += j;
update(i,j);
}
if(strcmp(oper,"Sub")==0) //表示第i个营地减少j个人
{
a[i] -= j;
update(i,-j);
}
}
}
return 0;
}
树状数组能快速求任意区间的和:
A[i] + A[i+1] + … + A[j],
设sum(k) = A[1]+A[2]+…+A[k],
则A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。
二,每次修改的是一个区间,所求的值是关于某个点的;
int Lowbit(int x) // 2^k
{
return x&(-x);
}
void update(int i,int x)
{
while(i>0)
{
c[i]+=x;
i-=Lowbit(i);
}
}
int sum(int x)
{
int sum=0;
while(x<=n)
{
sum+=c[x];
x+=Lowbit(x);
}
return sum;
}
计算几何:
向量基本用法
struct node {
double x; // 横坐标
double y; // 纵坐标
};
typedef node Vector;
Vector operator + (Vector A, Vector B) { return Vector(A.x + B.x, A.y + B.y); }
Vector operator - (Point A, Point B) { return Vector(A.x - B.y, A.y - B.y); }
Vector operator * (Vector A, double p) { return Vector(A.x*p, A.y*p); }
Vector operator / (Vector A, double p) { return Vector(A.x / p, A.y*p); }
double Dot(Vector A, Vector B) { return A.x*B.x + A.y*B.y; } // 向量点乘
double Length(Vector A) { return sqrt(Dot(A, A)); } // 向量模长
double Angle(Vector A, Vector B) { return acos(Dot(A, B) / Length(A) / Length(B)); } // 向量之间夹角
double Cross(Vector A, Vector B) { // 叉积计算 公式
return A.x*B.y - A.y*B.x;
}
Vector Rotate(Vector A, double rad) // 向量旋转 公式 {
return Vector(A.x*cos(rad) - A.y*sin(rad), A.x*sin(rad) + A.y*cos(rad));
}
Point getLineIntersection(Point P, Vector v, Point Q, Vector w) { // 两直线交点t1 t2计算公式
Vector u = P - Q;
double t = Cross(w, u) / Cross(v, w); // 求得是横坐标
return P + v*t; // 返回一个点
}
求多边形面积
node G[maxn];
int n;
double Cross(node a, node b) { // 叉积计算
return a.x*b.y - a.y*b.x;
}
int main()
{
while (scanf("%d", &n) != EOF && n) {
for (int i = 0; i < n; i++)
scanf("%lf %lf", &G[i].x, &G[i].y);
double sum = 0;
G[n].x = G[0].x;
G[n].y = G[0].y;
for (int i = 0; i < n; i++) {
sum += Cross(G[i], G[i + 1]);
}
// 或者
//for (int i = 0; i < n; i++) {
//sum += fun(G[i], G[(i + 1)% n]);
//}
sum = sum / 2.0;
printf("%.1f\n", sum);
}
system("pause");
return 0;
}
判断线段相交
node P[35][105];
double Cross_Prouct(node A,node B,node C) { // 计算BA叉乘CA
return (B.x-A.x)*(C.y-A.y)-(B.y-A.y)*(C.x-A.x);
}
bool Intersect(node A,node B,node C,node D) { // 通过叉乘判断线段是否相交;
if(min(A.x,B.x)<=max(C.x,D.x)&& // 快速排斥实验;
min(C.x,D.x)<=max(A.x,B.x)&&
min(A.y,B.y)<=max(C.y,D.y)&&
min(C.y,D.y)<=max(A.y,B.y)&&
Cross_Prouct(A,B,C)*Cross_Prouct(A,B,D)<0&& // 跨立实验;
Cross_Prouct(C,D,A)*Cross_Prouct(C,D,B)<0) // 叉乘异号表示在两侧;
return true;
else return false;
}
求三角形外心
Point circumcenter(const Point &a, const Point &b, const Point &c) { //返回三角形的外心
Point ret;
double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1*a1 + b1*b1) / 2;
double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2*a2 + b2*b2) / 2;
double d = a1*b2 - a2*b1;
ret.x = a.x + (c1*b2 - c2*b1) / d;
ret.y = a.y + (a1*c2 - a2*c1) / d;
return ret;
}
极角排序
double cross(point p1, point p2, point q1, point q2) { // 叉积计算
return (q2.y - q1.y)*(p2.x - p1.x) - (q2.x - q1.x)*(p2.y - p1.y);
}
bool cmp(point a, point b) {
point o;
o.x = o.y = 0;
return cross(o, b, o, a) < 0; // 叉积判断
}
sort(convex + 1, convex + cnt, cmp); // 按角排序, 从小到大
凸包
Graham模板(O(nlogn))
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node
{
int x,y;
} a[105],p[105];
int top,n;
double cross(node p0,node p1,node p2)//计算叉乘,注意p0,p1,p2的位置,这个决定了方向
{
return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);
}
double dis(node a,node b)//计算距离,这个用在了当两个点在一条直线上
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(node p1,node p2)//极角排序
{
double z=cross(a[0],p1,p2);
if(z>0||(z==0&&dis(a[0],p1)<dis(a[0],p2)))
return 1;
return 0;
}
void Graham()
{
int k=0;
for(int i=0; i<n; i++)
if(a[i].y<a[k].y||(a[i].y==a[k].y&&a[i].x<a[k].x))
k=i;
swap(a[0],a[k]);//找p[0]
sort(a+1,a+n,cmp);
top=1;
p[0]=a[0];
p[1]=a[1];
for(int i=2; i<n; i++)//控制进栈出栈
{
while(cross(p[top-1],p[top],a[i])<0&&top)
top--;
top++;
p[top]=a[i];
}
}
int main()
{
int m;
scanf("%d",&m);
while(m--)
{
scanf("%d",&n);
for(int i=0; i<n; i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
}
Graham();
for(int i=0; i<=top; i++)
{
printf("%d %d\n",p[i].x,p[i].y);
}
}
return 0;
}
Jarvis步进法(nh, h为凸包中顶点个数)
#include <stdio.h>
#include <algorithm>
using namespace std;
struct POINT
{
int x,y;
};
POINT point[100],pk;
int n,top,k,Stack[100];
int det(POINT a,POINT b,POINT c){
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(POINT a,POINT b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void Jarvis(int n,int flag)
{
int m,tmp;
POINT pm;
Stack[0]=0;//p0进栈
top=0;
while(m!=k)
{
pm=pk;m=k;
for(int i=1;i<n;i++)
{
tmp=det(point[Stack[top]],point[i],pm);
if((tmp>0&&flag==1)||(tmp<0&&flag==0)||
(tmp==0)&&(dis(point[Stack[top]],point[i])>dis(point[Stack[top]],pm)))
{
pm=point[i];
m=i;
}
}
top++;
Stack[top]=m;
}
if(flag==1)
{
for(int i=0;i<=top;i++)
printf("(%d,%d)",point[Stack[i]].x,point[Stack[i]].y);
}
if(flag==0)
{
for(int i=top-1;i>0;i--)
printf("(%d,%d)",point[Stack[i]].x,point[Stack[i]].y);
printf("\n");
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d%d",&(point[i].x),&(point[i].y));
if(point[i].y<point[0].y||point[i].y==point[0].y&&point[i].x<point[0].x)//最左最低点
swap(point[0],point[i]);
if(i==0)
{
pk=point[0];
k=0;
}
if(point[i].y>pk.y||point[i].y==pk.y&&point[i].x>pk.x)//最右最高点
{
pk=point[i];
k=i;
}
}
Jarvis(n,1);//右链
Jarvis(n,0);//左链
return 0;
}
一. 巴什博奕(Bash Game):
A和B一块报数,每人每次报最少1个,最多报4个,看谁先报到30。这应该是最古老的关于巴什博奕的游戏了吧。
其实如果知道原理,这游戏一点运气成分都没有,只和先手后手有关,比如第一次报数,A报k个数,那么B报5-k个数,那么B报数之后问题就变为,A和B一块报数,看谁先报到25了,进而变为20,15,10,5,当到5的时候,不管A怎么报数,最后一个数肯定是B报的,可以看出,作为后手的B在个游戏中是不会输的。
那么如果我们要报n个数,每次最少报一个,最多报m个,我们可以找到这么一个整数k和r,使n=k*(m+1)+r,代入上面的例子我们就可以知道,如果r=0,那么先手必败;否则,先手必胜。
巴什博奕:只有一堆n个物品,两个人轮流从中取物,规定每次最少取一个,最多取m个,最后取光者为胜。
代码如下:
#include <iostream>
using namespace std;
int main()
{
int n,m;
while(cin>>n>>m)
if(n%(m+1)==0) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
return 0;
}
例题有:HDU4764 Stone:
题目大意:Tang和Jiang轮流写数字,Tang先写,每次写的数x满足1<=x<=k,Jiang每次写的数y满足1<=y-x<=k,谁先写到不小于n的数算输。
结论:r=(n-1)%(k+1),r=0时Jiang胜,否则Tang胜。
二. 威佐夫博弈(Wythoff Game):
有两堆各若干的物品,两人轮流从其中一堆取至少一件物品,至多不限,或从两堆中同时取相同件物品,规定最后取完者胜利。
直接说结论了,若两堆物品的初始值为(x,y),且x<y,则另z=y-x;
记w=(int)[((sqrt(5)+1)/2)*z ];
若w=x,则先手必败,否则先手必胜。
代码如下:
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n1,n2,temp;
while(cin>>n1>>n2)
{
if(n1>n2) swap(n1,n2);
temp=floor((n2-n1)*(1+sqrt(5.0))/2.0);
if(temp==n1) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
}
return 0;
}
三. 尼姆博弈(Nimm Game):
尼姆博弈指的是这样一个博弈游戏:有任意堆物品,每堆物品的个数是任意的,双方轮流从中取物品,每一次只能从一堆物品中取部分或全部物品,最少取一件,取到最后一件物品的人获胜。
结论就是:把每堆物品数全部异或起来,如果得到的值为0,那么先手必败,否则先手必胜。
代码如下:
#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
int main()
{
int n,ans,temp;
while(cin>>n)
{
temp=0;
for(int i=0;i<n;i++)
{
cin>>ans;
temp^=ans;
}
if(temp==0) cout<<"后手必胜"<<endl;
else cout<<"先手必胜"<<endl;
}
return 0;
}
四 斐波那契博弈:
有一堆物品,两人轮流取物品,先手最少取一个,至多无上限,但不能把物品取完,之后每次取的物品数不能超过上一次取的物品数的二倍且至少为一件,取走最后一件物品的人获胜。
结论是:先手胜当且仅当n不是斐波那契数(n为物品总数)
如HDU2516
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 55;
int f[N];
void Init()
{
f[0] = f[1] = 1;
for(int i=2;i<N;i++)
f[i] = f[i-1] + f[i-2];
}
int main()
{
Init();
int n;
while(cin>>n)
{
if(n == 0) break;
bool flag = 0;
for(int i=0;i<N;i++)
{
if(f[i] == n)
{
flag = 1;
break;
}
}
if(flag) puts("Second win");
else puts("First win");
}
return 0;
}
高斯消元法:
数学上,高斯消元法(或译:高斯消去法)(英语:Gaussian Elimination),是线性代数中的一个算法,可用来为线性方程组求解,求出矩阵的秩,以及求出可逆方阵的逆矩阵。当用于一个矩阵时,高斯消元法会产生出一个“行梯阵式”。(来自维基百科)
构造如下方程:
a[0][0]*X0 + a[0][1] *X1 + a[0][2]*X2+...........a[0][n-1]*Xn-1 = a[0][n]
a[1][0]*X0 + a[1][1] *X1 + a[1][2]*X2+...........a[1][n-1]*Xn-1 ) = a[1][n]
..................................................
..................................................
a[m-1][0]*X0 + a[m-1][1] *X1 + a[m-1][2]*X2+...........a[m-1][n-1]*Xn-1 = a[m-1][n]
一共有m个方程,有n个未知量(X0,X1,...XN-1),未知量为所求,a[0....m-1][n]为常数。
在一些ACM题目中关键点就是构造方程,在求解概率期望的时候经常用到,找到题目中的状态递推方程。
常用E[k]表示从k节点到达目标节点还需要的期望的步数,那么目标节点p, E[p]=0,很关键。
构造出方程组,带入模板。
//高斯消元模板(整型)
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <cmath>
using namespace std;
const int maxn=105;
int equ,var; // 有equ个方程,var个变元。增广阵行数为equ, 分别为0到equ - 1,列数为var + 1,分别为0到var.
int a[maxn][maxn];
int x[maxn]; // 解集.
bool free_x[maxn]; // 判断是否是不确定的变元.
int free_num;
void Debug(void)
{
int i,j;
for(i=0;i<equ;i++)
{
for(j=0;j<var+1;j++)
{
cout<<a[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
}
inline int gcd(int a, int b)
{
int t;
while (b!=0)
{
t=b;
b=a%b;
a=t;
}
return a;
}
inline int lcm(int a, int b)
{
return a*b/gcd(a,b);
}
// 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
int Gauss(void)
{
int i,j,k;
int max_r; // 当前这列绝对值最大的行.
int col; // 当前处理的列.
int ta,tb;
int LCM;
int temp;
int free_x_num;
int free_index;
// 转换为阶梯阵.
col=0; // 当前处理的列.
for(k=0;k<equ&&col<var;k++,col++)
{ // 枚举当前处理的行.
// 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
max_r=k;
for(i=k+1;i<equ;i++)
{
if(abs(a[i][col])>abs(a[max_r][col]))
max_r=i;
}
if(max_r!=k)
{ // 与第k行交换.
for(j=k;j<var+1;j++)
swap(a[k][j],a[max_r][j]);
}
if(a[k][col]==0)
{ // 说明该col列第k行以下全是0了,则处理当前行的下一列.
k--; continue;
}
for(i=k+1;i<equ;i++)
{ // 枚举要删去的行.
if (a[i][col]!=0)
{
LCM=lcm(abs(a[i][col]),abs(a[k][col]));
ta=LCM/abs(a[i][col]),tb=LCM/abs(a[k][col]);
if(a[i][col]*a[k][col]<0) tb=-tb; // 异号的情况是两个数相加.
for(j=col;j<var+1;j++)
{
a[i][j]=a[i][j]*ta-a[k][j]*tb;
}
}
}
}
//Debug();
// 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
for(i=k;i<equ;i++)
{ // 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
if (a[i][col]!=0)
return -1;
}
// 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
// 且出现的行数即为自由变元的个数.
if(k<var)
{
// 首先,自由变元有var - k个,即不确定的变元至少有var - k个.
for (i=k-1;i>=0;i--)
{
// 第i行一定不会是(0, 0, ..., 0)的情况,因为这样的行是在第k行到第equ行.
// 同样,第i行一定不会是(0, 0, ..., a), a != 0的情况,这样的无解的.
free_x_num=0; // 用于判断该行中的不确定的变元的个数,如果超过1个,则无法求解,它们仍然为不确定的变元.
for(j=0;j<var;j++)
{
if(a[i][j]!=0&&free_x[j])
free_x_num++,free_index = j;
}
if(free_x_num>1)
continue; // 无法求解出确定的变元.
// 说明就只有一个不确定的变元free_index,那么可以求解出该变元,且该变元是确定的.
temp=a[i][var];
for(j=0;j<var;j++)
{
if(a[i][j]!=0&&j!=free_index)
temp-=a[i][j]*x[j];
}
x[free_index]=temp/a[i][free_index]; // 求出该变元.
free_x[free_index]=0; // 该变元是确定的.
}
return var-k; // 自由变元有var - k个.
}
// 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
// 计算出Xn-1, Xn-2 ... X0.
for (i=var-1;i>=0;i--)
{
temp=a[i][var];
for(j=i+1;j<var;j++)
{
if(a[i][j]!=0)
temp-=a[i][j]*x[j];
}
if(temp%a[i][i]!=0)
return -2; // 说明有浮点数解,但无整数解.
x[i]=temp/a[i][i];
}
return 0;
}
int main(void)
{
int i, j;
while (scanf("%d %d",&equ,&var)!=EOF)
{
memset(a,0,sizeof(a));
memset(x,0,sizeof(x));
memset(free_x,1,sizeof(free_x)); // 一开始全是不确定的变元
for(i=0;i<equ;i++)//构造增广矩阵
for(j=0;j<var+1;j++)
scanf("%d",&a[i][j]);
// Debug();
free_num=Gauss();
if(free_num==-1) printf("无解!\n");
else if(free_num==-2) printf("有浮点数解,无整数解!\n");
else if(free_num>0)
{
printf("无穷多解! 自由变元个数为%d\n",free_num);
for(i=0;i<var;i++)
{
if(free_x[i]) printf("x%d 是不确定的\n",i+1);
else printf("x%d: %d\n",i+1,x[i]);
}
}
else
{
for(i=0;i<var;i++)
printf("x%d: %d\n",i+1,x[i]);
}
printf("\n");
}
return 0;
}
//高斯消元模板(浮点型)
const int maxn=1002;
const double eps=1e-12;
double a[maxn][maxn];
int equ,var;//equ个方程,var个变量
double x[maxn];//解集
bool free_x[maxn];
int n;
int sgn(double x)
{
return (x>eps)-(x<-eps);
}
// 高斯消元法解方程组(Gauss-Jordan elimination).(0表示无解,1表示唯一解,大于1表示无穷解,并返回自由变元的个数)
int gauss()
{
equ=n,var=n;//多少个方程,多少个变量
int i,j,k;
int max_r; // 当前这列绝对值最大的行.
int col; // 当前处理的列.
double temp;
int free_x_num;
int free_index;
// 转换为阶梯阵.
col=0; // 当前处理的列.
memset(free_x,true,sizeof(free_x));
for(k=0;k<equ&&col<var;k++,col++)
{
max_r=k;
for(i=k+1;i<equ;i++)
{
if(sgn(fabs(a[i][col])-fabs(a[max_r][col]))>0)
max_r=i;
}
if(max_r!=k)
{ // 与第k行交换.
for(j=k;j<var+1;j++)
swap(a[k][j],a[max_r][j]);
}
if(sgn(a[k][col])==0)
{ // 说明该col列第k行以下全是0了,则处理当前行的下一列.
k--; continue;
}
for(i=k+1;i<equ;i++)
{ // 枚举要删去的行.
if (sgn(a[i][col])!=0)
{
temp=a[i][col]/a[k][col];
for(j=col;j<var+1;j++)
{
a[i][j]=a[i][j]-a[k][j]*temp;
}
}
}
}
for(i=k;i<equ;i++)
{
if (sgn(a[i][col])!=0)
return 0;
}
if(k<var)
{
for(i=k-1;i>=0;i--)
{
free_x_num=0;
for(j=0;j<var;j++)
{
if (sgn(a[i][j])!=0&&free_x[j])
free_x_num++,free_index=j;
}
if(free_x_num>1) continue;
temp=a[i][var];
for(j=0;j<var;j++)
{
if(sgn(a[i][j])!=0&&j!=free_index)
temp-=a[i][j]*x[j];
}
x[free_index]=temp/a[i][free_index];
free_x[free_index]=0;
}
return var-k;
}
for (i=var-1;i>=0;i--)
{
temp=a[i][var];
for(j=i+1;j<var;j++)
{
if(sgn(a[i][j])!=0)
temp-=a[i][j]*x[j];
}
x[i]=temp/a[i][i];
}
return 1;
}
组合数学:
一.第二类Stirling数
定理:第二类Stirling数S(p,k)计数的是把p元素集合划分到k个不可区分的盒子里且没有空盒子的划分个数。
证明:元素在拿些盒子并不重要,唯一重要的是各个盒子里装的是什么,而不管哪个盒子装了什么。
递推公式有:S(p,p)=1 (p>=0) S(p,0)=0 (p>=1) S(p,k)=k*S(p-1,k)+S(p-1,k-1)(1<=k<=p-1) 。
考虑将前p个正整数,1,2,.....p的集合作为要被划分的集合,把{1,2,.....p}分到k个非空且不可区分的盒子的划分有两种情况:
(1)那些使得p自己单独在一个盒子的划分,存在有S(p-1,k-1)种划分个数
(2)那些使得p不单独自己在一个盒子的划分,存在有 k*S(p-1,k)种划分个数
考虑第二种情况,p不单独自己在一个盒子,也就是p和其他元素在一个集合里面,也就是说在没有放p之前,有p-1个元素已经分到了k个非空且不可区分的盒子里面(划分个数为S(p-1,k),那么现在问题是把p放在哪个盒子里面呢,有k种选择,所以存在有k*S(p-1,k)。
模板:
long long s[maxn][maxn];//存放要求的Stirling数
const long long mod=1e9+7;//取模
void init()//预处理
{
memset(s,0,sizeof(s));
s[1][1]=1;
for(int i=2;i<=maxn-1;i++)
for(int j=1;j<=i;j++)
{
s[i][j]=s[i-1][j-1]+j*s[i-1][j];
if(s[i][j]>=mod)
s[i][j]%=mod;
}
}
注意:要用long long类型,当元素个数>20,就超int类型了。
扩展:k! *S(p,k) 计数的是把p元素集合划分到k个可区分的盒子里且没有空盒子的划分个数。
二.Bell数
定理:Bell数B(p)是将p元素集合分到非空且不可区分盒子的划分个数(没有说分到几个盒子里面)。
B(p)=S(p,0)+S(p,1)+.....+S(p,k)
所以要求Bell数就要先求出第二类Stiring数。
三.第一类Stirling数
定理:第一类Stirling数s(p,k)计数的是把p个对象排成k个非空循环排列的方法数。
证明:把上述定理叙述中的循环排列叫做圆圈。递推公式为:
s(p,p)=1 (p>=0) 有p个人和p个圆圈,每个圆圈就只有一个人
s(p,0)=0 (p>=1) 如果至少有1个人,那么任何的安排都至少包含一个圆圈
s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1)
设人被标上1,2,.....p。将这p个人排成k个圆圈有两种情况。第一种排法是在一个圆圈里只有标号为p的人自己,排法有s(p-1,k-1)个。第二种排法中,p至少和另一个人在一个圆圈里。这些排法可以通过把1,2....p-1排成k个圆圈再把p放在1,2....p-1任何一人的左边得到,因此第二种类型的排法共有(p-1)*s(p-1,k)种排法。
在证明中我们所做的就是把{1,2,...,p}划分到k个非空且不可区分的盒子,然后将每个盒子中的元素排成一个循环排列。
long long s[maxn][maxn];//存放要求的第一类Stirling数
const long long mod=1e9+7;//取模
void init()//预处理
{
memset(s,0,sizeof(s));
s[1][1]=1;
for(int i=2;i<=maxn-1;i++)
for(int j=1;j<=i;j++)
{
s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j];
if(s[i][j]>=mod)
s[i][j]%=mod;
}
}
组合数取模
1.利用整数唯一分解定理,求(n+1-m) * (n+m)! / ( m! * (n+1)! )
任何正整数都有且只有一种方法写出其素因子幂相乘的形式。比如18= 2 * 3^2
A=(p1^k1)*(p2^k2)*(p3^k3)*(p4^k4)*......*(pn^kn) pi为素数
还有把阶层看作一个数,比m! 怎样求m!里面素数2的指数呢?
cnt=0; while(m) { m/=2; cnt+=m; } 就可以了,为什么呢?考虑m=4,则m!= 4*3*2*1, 第一次m/=2,是计算m!里面有多少个数能整除2的(有4,2),所以cnt+=2,有两个数贡献
了两个素数2,接下来第二次m/=2,是计算m!里面有多少个数能整除4的,有1个数又贡献了一个素数2.
所以先预先筛出最大范围内的素数,然后考虑每个素数就好了,关键是求出整个式子的该素数的指数是多少。
bool isprime[maxn*2+10];
int prime[maxn*2+10];
int len=0;//素数的个数
int n,m;
void sieve(int n)//筛n以内的素数
{
for(int i=0;i<=n;i++)
isprime[i]=1;
isprime[0]=isprime[1]=0;
for(int i=2;i<=n;i++)
if(isprime[i])
{
prime[len++]=i;
for(int j=2*i;j<=n;j+=i)
isprime[j]=0;
}
}
int cal(int p,int n)//计算n!里面有多少个p相乘
{
int ans=0;
while(n)
{
n/=p;
ans+=n;
}
return ans;
}
int main()
{
sieve(maxn*2);
long long ans=1;//记得要用long long
cin>>n>>m;
int nm=n+1-m;
for(int i=0;i<len&&prime[i]<=(n+m);i++)//prime[i]<=(n+m)是因为拆成素数幂相乘的形式该素数不会大于n+m,最大(n+m)! (n+m)*(n+m-1)*(n+m-2).....
{
int cnt=0;//分解为素数prime[i]的指数是多少
while(nm%prime[i]==0)//nm中有多少个prime[i],也就是把nm分解后prime[i]的指数
{
nm/=prime[i];
cnt++;
}
cnt=cnt+cal(prime[i],n+m)-cal(prime[i],m)-cal(prime[i],n+1);//加上分子的指数再减去分母的指数
for(int j=1;j<=cnt;j++)
{
ans=ans*prime[i];
if(ans>=mod)
ans%=mod;
}
}
cout<<ans<<endl;
return 0;
}
Miller_Rabbin算法判断大素数,Pollard_rho算法进行质因素分解
Miller-rabin
Miller-rabin算法是一个用来快速判断一个正整数是否为素数的算法。它利用了费马小定理,即:如果p是质数,且a,p互质,那么a^(p-1) mod p恒等于1。也就是对于所有小于p的正整数a来说都应该复合a^(p-1) mod p恒等于1。那么根据逆否命题,对于一个p,我们只要举出一个a(a<p)不符合这个恒等式,则可判定p不是素数。Miller-rabin算法就是多次用不同的a来尝试p是否为素数。
但是每次尝试过程中还做了一个优化操作,以提高用少量的a检测出p不是素数的概率。这个优化叫做二次探测。它是根据一个定理:如果p是一个素数,那么对于x(0<x<p),若x^2 mod p 等于1,则x=1或p-1。逆否命题:如果对于x(0<x<p),若x^2 mod p 不等于1,则p不是素数。根据这个定理,我们要计算a^(p-1) mod p是否等于1时,可以这样计算,设p-1=(2^t) * k。我们从a^k开始,不断将其平方直到得到a^(p-1),一旦发现某次平方后mod p等于1了,那么说明符合了二次探测定理的逆否命题使用条件,立即检查x是否等于1或p-1,如果不是则可直接判定p为合数。
pollard-rho
这是一个用来快速对整数进行质因数分解的算法,需要与Miller-rabin共同使用。求n的质因子的基本过程是,先判断n是否为素数,如果不是则按照一个伪随机数生成过程来生成随机数序列,对于每个生成的随机数判断与n是否互质,如果互质则尝试下一个随机数。如果不互质则将其公因子记作p,递归求解p和n/p的因子。如果n是素数则直接返回n为其素因子。
至于这个随机数序列是如何生成的暂时还不能理解,而且也是有多种不同的方式。这个序列生成过程中会产生循环,遇到循环则立即退出。
Pollard rho算法的原理就是通过某种方法得到两个整数a和b,而待分解的大整数为n,计算p=gcd(a-b,n),直到p不为1,或者a,b出现循环为止。然后再判断p是否为n,如果p=n成立,那么返回n是一个质数,否则返回p是n的一个因子,那么我们又可以递归的计算Pollard(p)和Pollard(n/p),这样,我们就可以求出n的所有质因子。
具体操作中,我们通常使用函数x2=x1*x1+c来计算逐步迭代计算a和b的值,实践中,通常取c为1,即b=a*a+1,在下一次计算中,将b的值赋给a,再次使用上式来计算新的b的值,当a,b出现循环时,即可退出进行判断。
在实际计算中,a和b的值最终肯定一出现一个循环,而将这些值用光滑的曲线连接起来的话,可以近似的看成是一个ρ型的。对于Pollard rho,它可以在O(sqrt(p))的时间复杂度内找到n的一个小因子p,可见效率还是可以的,但是对于一个因子很少、因子值很大的大整数n来说,Pollard rho算法的效率仍然不是很好,那么,我们还得寻找更加的方法了。
Miller-rabin算法用到了二次探测,因为有些数称为Carmichael数,它满足费马小定理,但不是素数。
例题: 题目大意:输入一个数(<2^54),判断其是否为质数,如果否,输出其最小质因数
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <cmath>
#include <time.h>
#include <iomanip>
#include <cctype>
using namespace std;
/**
Miller_Rabin 算法进行素数测试
快速判断一个<2^63的数是不是素数,主要是根据费马小定理
*/
#define ll long long
const int S=8; ///随机化算法判定次数
///计算ret=(a*b)%c a,b,c<2^63
ll mult_mod(ll a,ll b,ll c)
{
a%=c;
b%=c;
ll ret=0;
ll temp=a;
while(b)
{
if(b&1)
{
ret+=temp;
if(ret>c)
ret-=c;//直接取模慢很多
}
temp<<=1;
if(temp>c)
temp-=c;
b>>=1;
}
return ret;
}
///计算ret=(a^n)%mod
ll pow_mod(ll a,ll n,ll mod)
{
ll ret=1;
ll temp=a%mod;
while(n)
{
if(n&1)
ret=mult_mod(ret,temp,mod);
temp=mult_mod(temp,temp,mod);
n>>=1;
}
return ret;
}
///通过费马小定理 a^(n-1)=1(mod n)来判断n是否为素数
///中间使用了二次判断,令n-1=x*2^t
///是合数返回true,不一定是合数返回false
bool check(ll a,ll n,ll x,ll t)
{
ll ret=pow_mod(a,x,n);
ll last=ret;//记录上一次的x
for(int i=1;i<=t;i++)
{
ret=mult_mod(ret,ret,n);
if(ret==1&&last!=1&&last!=n-1)
return true;//二次判断为是合数
last=ret;
}
if(ret!=1)
return true;//是合数,费马小定理
return false;
}
///Miller_Rabbin算法
///是素数返回true(可能是伪素数),否则返回false
bool Miller_Rabbin(ll n)
{
if(n<2) return false;
if(n==2) return true;
if((n&1)==0) return false;//偶数
ll x=n-1;
ll t=0;
while((x&1)==0)
{
x>>=1;
t++;
}
srand(time(NULL));
for(int i=0;i<S;i++)
{
ll a=rand()%(n-1)+1; // 生成随机数 0<a<=n-1 去试试
if(check(a,n,x,t))
return false;
}
return true;
}
/**
pollard_rho算法进行质因素分解
*/
ll factor[100];//质因素分解结果(一开始无序)
int tot;//质因素的个数 0~to-1
ll gcd(ll a,ll b)
{
ll t;
while(b)
{
t=a;
a=b;
b=t%b;
}
if(a>=0) return a;
return -a;
}
///找到一个质因素
ll pollard_rho(ll x,ll c)
{
ll i=1,k=2;
srand(time(NULL));
ll x0=rand()%(x-1)+1;//随即一个因子来判断
ll y=x0;
while(1)
{
i++;
x0=(mult_mod(x0,x0,x)+c)%x;
ll d=gcd(y-x0,x);
if(d!=1&&d!=x) return d;
if(y==x0) return x;
if(i==k)
{
y=x0;
k+=k;
}
}
}
///对n进行质因素分解,存入factor数组,k为了防止死循环,设置为107左右
void findfac(ll n,int k)
{
if(n==1)
return;
if(Miller_Rabbin(n))
{
factor[tot++]=n;
return;
}
ll p=n;
int c=k;
while(p>=n)
p=pollard_rho(p,c--);
findfac(p,k);
findfac(n/p,k);
}
int main()
{
int t;scanf("%d",&t);
ll n;
while(t--)
{
scanf("%I64d",&n);
if(Miller_Rabbin(n))
{
printf("Prime\n");
continue;
}
tot=0;
findfac(n,107);
sort(factor,factor+tot);
printf("%I64d\n",factor[0]);
}
return 0;
}
求区间[1, n]中与m互质的数的个数
int solve(II n,II m){
vector<II>p;
for(II i=2;i*i<=m;i++){
if(m%i==0){
p.push_back(i);
while(m%i==0) m/=i;
}
}
if(m>1) p.push_back(m);
II sz=p.size();
LL sum=0;
for(II i=1;i<(1<<sz);i++){
II ct=0;
LL mul=1;
for(II j=0;j<sz;j++){
if(i&(1<<j)){
ct++;
mul*=p[j];
}
}
if(ct&1) sum+=n/mul;
else sum-=n/mul;
}
return n-sum;
}
未完待续
......