邝斌模板扫盲 写在地区赛之前
前言:还有1个多月就是区域赛了,最近需要补全一下知识的广度,决定把斌神的模板消化一下。有些关键的或者自己不懂的就记录在这里。
1,扩展KMP。
HDU 4333 扩展KMP能求出一个串所有后缀串(即s[i...len])和模式串的最长公共前缀。于是只要将这个串复制一遍,求出该串每个后缀与其本身的最长公共前缀即可,当公共前缀>=len时,显然相等,否则只要比较下一位就能确定这个串与原串的大小关系。
至于重复串的问题,只有当这个串有循环节的时候才会产生重复串,用KMP的next数组求出最小循环节,用长度除以最小循环节得到循环节个数,在将3个答案都除以循环节个数即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 2000010; int Next[maxn]; int extend[maxn]; void pre_EKMP(char x[], int m, int Next[]){ Next[0] = m; int j = 0; while(j+1<m && x[j]==x[j+1]) j++; Next[1] = j; int k = 1; for(int i=2; i<m; i++){ int p = Next[k]+k-1; int L = Next[i-k]; if(i+L<p+1) Next[i]=L; else{ j=max(0, p-i+1); while(i+j<m && x[i+j]==x[j]) j++; Next[i] = j; k = i; } } } void EKMP(char x[], int m, char y[], int n,int Next[], int extend[]) { pre_EKMP(x, m, Next); int j = 0; while(j<n && j<m && x[j]==y[j]) j++; extend[0] = j; int k = 0; for(int i=1; i<n; i++){ int p = extend[k]+k-1; int L = Next[i-k]; if(i+L<p+1) extend[i]=L; else{ j=max(0,p-i+1); while(i+j<n && j<m && y[i+j]==x[j]) j++; extend[i] = j; k = i; } } } void getNext(char T[], int len, int Next[]) { int j,k; j=0; k=-1; Next[0]=-1; while(j<len){ if(k==-1||T[j]==T[k]) Next[++j] = ++k; else k = Next[k]; } } char str1[maxn], str2[maxn]; int main() { int T, ks = 0; scanf("%d", &T); while(T--) { scanf("%s", str1); int len = strlen(str1); strcpy(str2, str1); strcat(str2, str1); int len2 = strlen(str2); int cnt1 = 0, cnt2 = 0, cnt3 = 0; EKMP(str1, len, str2, len2, Next, extend); for(int i=0; i<len; i++){ if(extend[i]>=len) cnt2++; else{ if(str2[i+extend[i]]<str1[extend[i]]) cnt1++; else cnt3++; } } getNext(str1, len, Next); int t = len - Next[len]; int tot = 1; if(len%t == 0) tot = len/t; printf("Case %d: %d %d %d\n", ++ks, cnt1/tot, cnt2/tot, cnt3/tot); } return 0; }
注意,要想得到Next[len]的值不能直接得到,需要再做一次朴素KMP。
2,后缀数组
URAL 11297 最长回文字串
#include <bits/stdc++.h> using namespace std; const int maxn = 2010; int sa[maxn];//SA数组,表示将S的n个后缀从小到大排序后把排好序的 //的后缀的开头位置顺次放入SA中 int t1[maxn],t2[maxn],c[maxn];//求SA数组需要的中间变量,不需要赋值 int Rank[maxn],height[maxn]; //待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m, //除s[n-1]外的所有s[i]都大于0,r[n-1]=0 //函数结束以后结果放在sa数组中 void build_sa(int s[],int n,int m) { int i,j,p,*x=t1,*y=t2; //第一轮基数排序,如果s的最大值很大,可改为快速排序 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=s[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1) { p=0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++)y[p++]=i;//后面的j个数第二关键字为空的最小 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++; if(p>=n)break; m=p;//下次基数排序的最大值 } } void getHeight(int s[],int n) { int i,j,k=0; for(i=0;i<=n;i++)Rank[sa[i]]=i; for(i=0;i<n;i++) { if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } int n, m, dp[maxn][30]; void Lcp_init(){ for(int i=1; i<=n+m+1; i++) dp[i][0] = height[i]; for(int j=1; (1<<j)-1<=n+m+1; j++){ for(int i=1; i+(1<<j)<=n+m; i++){ dp[i][j] = min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]); } } } int lcp(int l, int r){ l = Rank[l], r = Rank[r]; if(l>r) swap(l, r); ++l; int k=0,len=r-l+1; while((1<<(k+1))<=len) ++k; return min(dp[l][k], dp[r-(1<<k)+1][k]); } char str[maxn]; int r[maxn]; int main() { while(scanf("%s", str) == 1){ int len = strlen(str); n = 2*len + 1; for(int i=0; i<len; i++) r[i] = str[i]; for(int i=0; i<len; i++) r[len+i+1] = str[len-1-i]; r[len] = 1; r[n] = 0; build_sa(r, n+1, 256); getHeight(r, n); Lcp_init(); int ans = 0, st, tmp; for(int i=0; i<len; i++){ tmp = lcp(i, n-i); if(2*tmp>ans){//偶对称 ans=2*tmp; st=i-tmp; } tmp = lcp(i,n-i-1); if(2*tmp-1>ans){//奇对称 ans=2*tmp-1; st=i-tmp+1; } } str[st+ans]=0; printf("%s\n", str+st); } return 0; }
PKU 2406 给定一个字符串L,已知这个字符串是由某个字符串S重复R次而得到的,求R的最大值。 利用KMP的性质就可以得到答案。
#include <stdio.h> #include <string.h> using namespace std; const int maxn = 1000005; int Next[maxn]; void getNext(char *s){ int len = strlen(s); int j=0, k=-1; Next[0] = -1; while(j < len){ if(k == -1 || s[j]==s[k]){ Next[++j] = ++k; } else{ k = Next[k]; } } } char s[maxn]; int main() { while(gets(s)){ if(strcmp(s,".") == 0){ break; } getNext(s); int ans=1; int len=strlen(s); if(len%(len-Next[len])==0){ ans = len/(len-Next[len]); } printf("%d\n", ans); } return 0; }
POJ 2774 Long Long Message
这道题要求的是两个串的最长公共连续子串,在求出height数组之后,再把sa数组区分出来,只要其中一个sa数组是属于第一串,另外一个属于第二串,那么我们可以求得其最大值,之所以可以这样做,是因为sa数组已经对字符串按字典序排好序了。
#include <stdio.h> #include <string.h> #include <iostream> using namespace std; const int maxn = 200005; int sa[maxn];//SA数组,表示将S的n个后缀从小到大排序后把排好序的 //的后缀的开头位置顺次放入SA中 int t1[maxn],t2[maxn],c[maxn];//求SA数组需要的中间变量,不需要赋值 int Rank[maxn],height[maxn]; //待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m, //除s[n-1]外的所有s[i]都大于0,r[n-1]=0 //函数结束以后结果放在sa数组中 void build_sa(int s[],int n,int m) { int i,j,p,*x=t1,*y=t2; //第一轮基数排序,如果s的最大值很大,可改为快速排序 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=s[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1) { p=0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++)y[p++]=i;//后面的j个数第二关键字为空的最小 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++; if(p>=n)break; m=p;//下次基数排序的最大值 } } int n, r[maxn]; void getHeight(int s[],int n) { int i,j,k=0; for(i=0;i<=n;i++)Rank[sa[i]]=i; for(i=0;i<n;i++) { if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } char str1[maxn], str2[maxn]; int main() { while(~scanf("%s%s", str1,str2)) { int len1 = strlen(str1); int len2 = strlen(str2); n = len1 + len2 + 1; for(int i=0; i<len1; i++){ r[i] = str1[i]; } for(int i=0; i<len2; i++){ r[len1+i+1] = str2[i]; } r[len1] = 1; r[n] = 0; build_sa(r, n+1, 256); getHeight(r, n); int ans = 0; for(int i=2; i<n; i++){ if(height[i] > ans){ if(sa[i-1]>=0 && sa[i-1]<len1 && sa[i]>=len1) ans = max(ans, height[i]); if(sa[i]>=0 && sa[i]<len1 && sa[i-1]>=len1) ans = max(ans, height[i]); } } printf("%d\n", ans); } return 0; }
POJ 3415 Common Substrings
题意:长度不小于k的公共字串的个数。
解法:后缀数组+单调栈。
(题解来自:http://www.cnblogs.com/luxiaoming/p/5270984.html)
暴力的想法自然是枚举第一个子串的起始位置i和第二个子串的起始位置j,肯定会T的
看了题解才知道有单调栈这种有优化方法。。
将后缀分为A组(起始点为第一个字符串)、B组
设符合要求的lcp长度为a,则其对答案的贡献为a-k+1(长度为k~a的都是符合要求的)
一开始这里我也是有疑问的,比如说k=1,aa和aab两个suffix,lcp=2,这样的话贡献为2-1+1=2
但是我们可以看到有这两个suffix符合要求的是有3个的(长度1的2个,2的1个),我们似乎只统计了长度1的和长度2的各一个
确实如此。但是我们不能小范围的看问题,应该要结合整个sa[]和height[]
这里没统计不意味着之后不统计,还会有一个a后缀和一个ab后缀,这里又贡献了1.这样就补齐了~
我们知道两个suffix的lcp是height中对应段height[_rank[i]+1]~height[_rank[j]]的最小值,应用这个性质我们可以来维护单调递增的栈
为什么要这样呢?可以理解为栈里是可能被用到的候选序列,如果当前扫描到的height小于栈顶(候选最大值),则根据上面的性质,
可以得出大于height的值是无法做出贡献了(或者说贡献变小了),那累加器的值要更新
同时我们这里用到了一个优化,为了防止栈内元素过多,我们把同一个height值,捆绑一个个数num,这样可以提升统计效率
我们对A、B组各做一次,加起来就是答案
一开始我在这里没有完全理解,总感觉会重复统计。其实是没有的。为什么呢?
那统计B组的时候为例。
对于B组后缀j,我统计答案都是在sa[j]之前找。
比如说找到A组中的ii,jj,kk三个后缀是符合的,那必定有sa[ii]、sa[jj]、sa[kk]都小于sa[j]
所以在统计A组时,sa[ii]也是在sa[ii]之前找,不可能找到sa[j]
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int maxn = 200010; int sa[maxn];//SA数组,表示将S的n个后缀从小到大排序后把排好序的 //的后缀的开头位置顺次放入SA中 int t1[maxn],t2[maxn],c[maxn];//求SA数组需要的中间变量,不需要赋值 int Rank[maxn],height[maxn]; //待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m, //除s[n-1]外的所有s[i]都大于0,r[n-1]=0 //函数结束以后结果放在sa数组中 void build_sa(int s[],int n,int m) { int i,j,p,*x=t1,*y=t2; //第一轮基数排序,如果s的最大值很大,可改为快速排序 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=s[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1) { p=0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++)y[p++]=i;//后面的j个数第二关键字为空的最小 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++; if(p>=n)break; m=p;//下次基数排序的最大值 } } int k, n, r[maxn]; void getHeight(int s[],int n) { int i,j,k=0; for(i=0;i<=n;i++)Rank[sa[i]]=i; for(i=0;i<n;i++) { if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } char str1[maxn], str2[maxn]; int sta[maxn], stb[maxn]; int main() { while(scanf("%d", &k) == 1 && k) { scanf("%s", str1); scanf("%s", str2); int len1 = strlen(str1); int len2 = strlen(str2); n = len1 + len2 + 1; for(int i=0; i<len1; i++) r[i] = str1[i]; for(int i=0; i<len2; i++) r[len1+i+1] = str2[i]; r[len1] = 1; r[n] = 0; build_sa(r,n+1,256); getHeight(r, n); LL ans = 0; LL sum = 0; int top = 0; for(int i=2; i<=n; i++){ if(height[i]<k){ sum=0; top=0; continue; } int cnt=0; if(sa[i-1]<len1){ cnt++; sum = sum + height[i]-k+1; } while(top>0 && height[i]<=sta[top-1]){ top--; sum -= (LL)stb[top]*(sta[top]-height[i]); cnt += stb[top]; } sta[top] = height[i]; stb[top++] = cnt; if(sa[i] > len1){ ans += sum; } } // sum = 0; top = 0; for(int i=2; i<=n; i++){ if(height[i]<k){ sum=0; top=0; continue; } int cnt = 0; if(sa[i-1]>len1){ cnt++; sum = sum+height[i]-k+1; } while(top>0 && height[i]<=sta[top-1]){ top--; sum -= (LL)stb[top]*(sta[top]-height[i]); cnt += stb[top]; } sta[top] = height[i]; stb[top++] = cnt; if(sa[i] < len1){ ans += sum; } } printf("%lld\n", ans); } return 0; }
POJ 3294 Life Forms
二分+后缀数组
题意:给出n个字符串,求出现在一半以上字符串的最长子串,按照字典序输出所有结果
解法:然后二分答案,进行分组,判断每组的后缀是否出现在不少于k个的原串中。
通过这道题可以学到,如何利用后缀数组输出我们通过二分拿到的答案串。
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int maxn = 200010; int sa[maxn];//SA数组,表示将S的n个后缀从小到大排序后把排好序的 //的后缀的开头位置顺次放入SA中 int t1[maxn],t2[maxn],c[maxn];//求SA数组需要的中间变量,不需要赋值 int Rank[maxn],height[maxn]; //待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m, //除s[n-1]外的所有s[i]都大于0,r[n-1]=0 //函数结束以后结果放在sa数组中 void build_sa(int s[],int n,int m) { int i,j,p,*x=t1,*y=t2; //第一轮基数排序,如果s的最大值很大,可改为快速排序 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=s[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1) { p=0; //直接利用sa数组排序第二关键字 for(i=n-j;i<n;i++)y[p++]=i;//后面的j个数第二关键字为空的最小 for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j; //这样数组y保存的就是按照第二关键字排序的结果 //基数排序第一关键字 for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; //根据sa和x数组计算新的x数组 swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++; if(p>=n)break; m=p;//下次基数排序的最大值 } } void getHeight(int s[],int n) { int i,j,k=0; for(i=0;i<=n;i++)Rank[sa[i]]=i; for(i=0;i<n;i++) { if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } int n, totlen; char str[110][1010]; int st[110],ed[110]; bool used[110]; int who[maxn]; int r[maxn]; int check(int totlen, int len, int k) { memset(used, 0, sizeof(used)); int ret = 0; int tmp = who[sa[1]]; if(tmp != -1 && used[tmp] == 0){ ret++; used[tmp] = 1; } if(ret >= k) return 1; for(int i=2; i<=totlen; i++){ if(height[i]<len){ ret = 0; memset(used, 0, sizeof(used)); tmp = who[sa[i]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp]=1; } if(ret>=k) return 1; } else{ tmp = who[sa[i]]; if(tmp != -1 && used[tmp]==false){ ret++; used[tmp]=1; } if(ret>=k) return i; } } return -1; } void output(int totlen, int len, int k) { memset(used, 0, sizeof(used)); int ret = 0; int tmp = who[sa[1]]; if(tmp != -1 && used[tmp] == 0){ ret++; used[tmp] = 1; } for(int i=2; i<=totlen; i++){ if(height[i]<len){ if(ret >= k){ for(int j=0; j<len; j++){ printf("%c", r[sa[i-1]+j]); } printf("\n"); } ret = 0; memset(used, 0, sizeof(used)); tmp = who[sa[i]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = 1; } } else{ tmp = who[sa[i]]; if(tmp != -1 && used[tmp] == false){ ret++; used[tmp] = 1; } } } if(ret >= k){ for(int j=0; j<len; j++) printf("%c", r[sa[totlen]+j]); printf("\n"); } } int main() { bool flag = 1; while(scanf("%d", &n) == 1 && n){ if(flag) flag = 0; else printf("\n"); totlen = 0; for(int i=0; i<n; i++){ scanf("%s", str[i]); int len = strlen(str[i]); for(int j=0; j<len; j++){ r[totlen+j] = str[i][j]; who[totlen+j] = i; } r[totlen+len]=i+130; who[totlen+len]=-1; totlen+=len+1; } totlen--; r[totlen]=0; build_sa(r,totlen+1,300); getHeight(r, totlen); int k = n/2+1; int ans=-1; int l=1,r=1010; while(l<=r){ int mid=(l+r)/2; int t = check(totlen,mid,k); if(t==-1){ r=mid-1; } else{ ans=mid; l=mid+1; } } if(ans<=0){ printf("?\n"); } else{ output(totlen,ans,k); } } return 0; }
3,后缀自动机,等待学习。
http://blog.csdn.net/qq_35649707/article/details/66473069
https://wenku.baidu.com/view/fa02d3fff111f18582d05a81.html
4,大区间素数筛选
POJ 2689 给出一个区间[L,U],找出区间内容、相邻的距离最近的两个素数和 距离最远的两个素数
#include <stdio.h> #include <string.h> using namespace std; const int maxn = 100010; int prime[maxn]; void getPrime(){ memset(prime,0,sizeof(prime)); for(int i=2; i<maxn; i++){ if(!prime[i]) prime[++prime[0]]=i; for(int j=1; j<=prime[0]&&prime[j]<maxn/i; j++){ prime[prime[j]*i]=1; if(i%prime[j]==0) break; } } } bool notprime[1000010]; int prime2[1000010]; void getPrime2(int L, int R){ memset(notprime, 0, sizeof(notprime)); if(L<2) L=2; for(int i=1; i<=prime[0]&&(long long)prime[i]*prime[i]<=R; i++){ int s = L/prime[i]+(L%prime[i]>0); if(s==1) s=2; for(int j=s; (long long)j*prime[i]<=R; j++){ if((long long)j*prime[i]>=L){ notprime[j*prime[i]-L]=1; } } } prime2[0]=0; for(int i=0; i<=R-L; i++) if(!notprime[i]) prime2[++prime2[0]] = i+L; } int main() { getPrime(); int L, R; while(~scanf("%d %d", &L,&R)) { getPrime2(L,R); if(prime2[0]<2) puts("There are no adjacent primes."); else{ int x1=0,x2=100000000,y1=0,y2=0; for(int i=1; i<prime2[0]; i++){ if(prime2[i+1]-prime2[i]<x2-x1){ x1=prime2[i]; x2=prime2[i+1]; } if(prime2[i+1]-prime2[i]>y2-y1){ y1=prime2[i]; y2=prime2[i+1]; } } printf("%d,%d are closest, %d,%d are most distant.\n",x1,x2,y1,y2); } } return 0; }
5,高斯消元
POJ 1830 开关灯问题
A[[I][J]表明:对第J个灯实行一次操作后,第I个灯的状态也随之改变
#include <stdio.h> #include <string.h> #include <iostream> #include <stdlib.h> using namespace std; const int maxn = 50; int a[maxn][maxn]; int st[maxn],en[maxn]; int Guass(int equ, int var) { int max_r;//当前这列绝对值最大的行 int col,i,j,k;//当前处理的列 col = 0; for(k=0; k<equ&&col<var; k++,col++){ 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){ for(j=k; j<var+1; j++) swap(a[k][j], a[max_r][j]); } if(a[k][col]==0){ k--; continue; } for(i=k+1; i<equ; i++){ if(a[i][col]!=0){ for(j=col; j<var+1; j++) a[i][j]^=a[k][j]; } } } for(i=k; i<equ; i++){ if(a[i][col]!=0) return -1; } return var-k; } int main() { int T,n; scanf("%d", &T); while(T--){ scanf("%d", &n); for(int i=0; i<n; i++) scanf("%d", &st[i]); for(int i=0; i<n; i++) scanf("%d", &en[i]); memset(a, 0, sizeof(a)); int u, v; while(scanf("%d %d", &u,&v)){ if(u==0&&v==0) break; a[v-1][u-1]=1; } for(int i=0; i<n; i++) a[i][i]=1; for(int i=0; i<n; i++) a[i][n]=st[i]^en[i]; int ans = Guass(n,n); if(ans == -1) printf("Oh,it's impossible~!!\n"); else printf("%d\n", (1<<ans)); } return 0; }
POJ 2947 Widget Factory
题意:
a1,a2......ap (1<=ai<=n)
第一行表示从星期start 到星期end 一共生产了p 件装饰物(工作的天数为end-start+1+7*x,
加7*x 是因为它可能生产很多周),第二行表示这p 件装饰物的种类(可能出现相同的种类,
即ai=aj)。规定每件装饰物至少生产3 天,最多生产9 天。问每种装饰物需要生产的天数。
如果没有解,则输出“Inconsistent data.”,如果有多解,则输出“Multiple solutions.”,如果
只有唯一解,则输出每种装饰物需要生产的天数。
解法:
的天数为b,则可以列出下列方程:
a1*x1+a2*x2+...an*xn = b (mod 7)
这样一共可以列出m个方程式,然后使用高斯消元来解。
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> using namespace std; const int mod = 7; const int maxn = 400; int a[maxn][maxn]; int x[maxn]; inline int lcm(int a, int b){ return a/__gcd(a,b)*b; } long long inv(long long a, long long m){ if(a==1) return 1; return inv(m%a,m)*(m-m/a)%m; } int Guass(int equ, int var){ int max_r,col,k; for(k=0,col=0; k<equ&&col<var; k++,col++){ max_r=k; for(int i=k+1; i<equ; i++) if(abs(a[i][col])>abs(a[max_r][col])) max_r=i; if(a[max_r][col]==0){ k--; continue; } if(max_r!=k){ for(int j=col; j<var+1; j++){ swap(a[k][j], a[max_r][j]); } } for(int i=k+1; i<equ; i++){ if(a[i][col]!=0){ int LCM = lcm(abs(a[i][col]), abs(a[k][col])); int ta = LCM/abs(a[i][col]); int tb = LCM/abs(a[k][col]); if(a[i][col]*a[k][col]<0) tb=-tb; for(int j=col; j<var+1; j++) a[i][j] = ((a[i][j]*ta-a[k][j]*tb)%mod+mod)%mod; } } } for(int i=k; i<equ; i++){//无解 if(a[i][col]!=0){ return -1; } } if(k<var) return var-k;//多解 for(int i=var-1; i>=0; i--){ int temp = a[i][var]; for(int j=i+1; j<var; j++){ if(a[i][j]!=0){ temp -= a[i][j]*x[j]; temp = (temp%mod + mod)%mod; } } x[i] = (temp*inv(a[i][i],mod))%mod; } return 0;//唯一解 } int change(char s[]){ if(strcmp(s,"MON")==0) return 1; else if(strcmp(s,"TUE")==0) return 2; else if(strcmp(s,"WED")==0) return 3; else if(strcmp(s,"THU")==0) return 4; else if(strcmp(s,"FRI")==0) return 5; else if(strcmp(s,"SAT")==0) return 6; else return 7; } char s1[10], s2[10]; int main() { int n, m; while(~scanf("%d %d", &n,&m)){ if(n == 0 && m == 0) break; memset(a, 0, sizeof(a)); int k; for(int i=0; i<m; i++){ scanf("%d%s%s", &k,s1,s2); a[i][n]=((change(s2)-change(s1)+1)%mod+mod)%mod; int t; while(k--){ scanf("%d", &t); t--; a[i][t]++; a[i][t]%=mod; } } int ans = Guass(m,n); if(ans==0){ for(int i=0; i<n; i++){ if(x[i]<=2) x[i]+=7; } for(int i=0; i<n-1; i++) printf("%d ", x[i]); printf("%d\n", x[n-1]); } else if(ans==-1) printf("Inconsistent data.\n"); else printf("Multiple solutions.\n"); } return 0; }
6,求A^B的约数和对MOD取模
根据唯一分解定理将A进行因式分解可得:A = p1^a1 * p2^a2 * p3^a3 * pn^an.
A^B=p1^(a1*B)*p2^(a2*B)*...*pn^(an*B);
A^B的所有约数之和sum=[1+p1+p1^2+...+p1^(a1*B)]*[1+p2+p2^2+...+p2^(a2*B)]*[1+pn+pn^2+...+pn^(an*B)].
若n为奇数,一共有偶数项,设p为3,则(1+p)+(p^2+p^3)=(1+p)+p^2(1+p)=(1+p^2)*(1+p)
若n为偶数,一共有奇数项,设p为4,则(1+p)+p^2+(p^3+p^4)=(1+p)+p^2+p^3(1+p)=(1+p^3)*(1+p)+P^2
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 10010; const int mod = 9901; int prime[maxn]; void getPrime(){ memset(prime, 0, sizeof(prime)); for(int i=2; i<maxn; i++){ if(!prime[i]) prime[++prime[0]]=i; for(int j=1; j<=prime[0] && prime[j]<=maxn/i; j++){ prime[prime[j]*i] = 1; if(i%prime[j]==0) break; } } } long long fac[100][2]; int facCnt; int getFactors(long long x){ facCnt = 0; long long tmp = x; for(int i=1; prime[i]<=tmp/prime[i]; i++){ fac[facCnt][1]=0; if(tmp%prime[i]==0){ fac[facCnt][0] = prime[i]; while(tmp%prime[i]==0){ fac[facCnt][1]++; tmp /= prime[i]; } facCnt++; } } if(tmp!=1){ fac[facCnt][0]=tmp; fac[facCnt++][1]=1; } return facCnt; } long long pow_m(long long a, long long n){ long long ret = 1; long long temp = a%mod; while(n){ if(n&1) ret=(ret*temp)%mod; temp = temp*temp%mod; n>>=1; } return ret; } long long sum(long long p, long long n){ if(p == 0) return 0; if(n == 0) return 1; if(n&1){ return ((1+pow_m(p,n/2+1))%mod*sum(p,n/2)%mod)%mod; } else return ((1+pow_m(p,n/2+1))%mod*sum(p,n/2-1)%mod+pow_m(p,n/2)%mod)%mod; } int main() { int A,B; getPrime(); while(scanf("%d %d", &A,&B)!=EOF) { getFactors(A); long long ans = 1; for(int i=0; i<facCnt; i++){ ans *= (sum(fac[i][0], B*fac[i][1]))%mod; ans %= mod; } ans %= mod; printf("%I64d\n", ans); } return 0; }
7,置换群,Polya定理
题目:http://acm.nyist.net/JudgeOnline/status.php?pid=900
解法:这个是裸的置换的题, 可以发现最少需要交换的顺序就是每一个循环节的循环长度的最小公倍数,. 注意res用long long
#include <bits/stdc++.h> using namespace std; const int maxn = 210; int Next[maxn]; bool vis[maxn]; int main() { int n; while(~scanf("%d", &n)) { memset(Next, 0, sizeof(Next)); memset(vis, 0, sizeof(vis)); for(int i=1; i<=n; i++){ scanf("%d", &Next[i]); } long long res = 1; for(int i=1; i<=n; i++){ if(vis[i]) continue; int tmp = i; long long cnt = 1; vis[i] = 1; while(Next[tmp]!=i){ tmp = Next[tmp]; vis[tmp] = 1; cnt++; } res = res/__gcd(res, cnt)*cnt; } printf("%lld\n", res); } return 0; }
POJ 3270 置换群
//题意: 就是然你交换几次使得原来的序列变得有序(增加).
//思路: 这道题还是要巧妙运用置换群.
首先我们求出所有轮换,对于一个轮换,我们燃心的用其中最小的元素去交换得到其他的显然是最优的方案.
例如, 数字是8 4 5 3 2 7
明显, 目标状态是2 3 4 5 7 8,能写为两个轮换:(8 2 7)(4 3 5).
观察其中一个轮换, 要使交换代价最小, 应该用循环里面最小的数字2,去与另外的两个数字, 7与8交换. 这样交换的代价是 :
sum - tmpmin + (len - 1) * tmpmin
其中, sum为这个循环所有数字的和, len为长度, tmpmin为这个环里面最小的数字 .
考虑到另外一种情况, 我们可以从别的循环里面调一个数字, 进入这个循环之中, 使交换代价更小. 例如初始状态:1 8 9 7 6
可分解为两个循环:(1)(8 6 9 7),明显,第二个循环为(8 6 9 7),最小的数字为6 .
我们可以抽调整个数列最小的数字1进入这个循环。使第二个循环变为:(8 1 9 7)
让这个1完成任务后,再和6交换,让6重新回到循环之后。这样做的代价明显是:
sum + tmpmin+ (len + 1) * min
其中,sum为这个循环所有数字的和,len为长度,tmpmin为这个环里面最小的数字,min是整个数列最小的数字
所以我们每次都去取这两种情况中较小的一种.
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; typedef long long LL; const int maxn = 1e5+7; const int inf = 0x3f3f3f3f; int n, Next[maxn], a[maxn], b[maxn]; bool vis[maxn]; int main() { while(scanf("%d", &n) != EOF) { memset(Next, 0, sizeof(Next)); memset(vis, 0, sizeof(vis)); int maxx = -inf, minn = inf; for(int i=1; i<=n; i++){ scanf("%d", &a[i]); b[i] = a[i]; maxx = max(maxx, a[i]); minn = min(minn, a[i]); } sort(b+1,b+n+1); for(int i=1; i<=n; i++) Next[b[i]] = a[i]; LL ans = 0; for(int i=1; i<=n; i++){ if(vis[a[i]]) continue; int tmp = a[i]; LL cnt=1, sum=0; sum += tmp; int tmpmin = tmp; vis[a[i]] = 1; while(Next[tmp]!=a[i]){ tmp = Next[tmp]; vis[tmp] = 1; cnt++; sum+=tmp; tmpmin = min(tmpmin, tmp); } ans += min(sum-tmpmin+(cnt-1)*tmpmin, sum+tmpmin+(cnt+1)*minn); } printf("%lld\n", ans); } return 0; }
HDU 3923 Poyla计数
这道题就是一个poyla定理的裸题, 题目描述可以转换成用n种颜色去染一个长度为m的项链, 问一共有多少种不同的染法, 其中可以通过旋转和翻转后形成一样的算一种方法.
解法: Poyla定理就不详细说了, 这里简单说说. 定理就一句话 :
方案数 res =1/|G|*(∑元素数^旋转循环数+∑元素数^翻转循环数)。其中|G|表示变化的总数. 在这道题中|G| 是2m种旋转m种,翻转m种 . 分开说.
旋转 : 举个例子 一个圈:
1->2->3->4->5->6,顺序时针旋转2格,5->6->1->2->3->4。其中1到3的位置,3到5的位置,
5到1的位置,成一个循环. 总的有(1,3,5)(2,4,6)两个循环, 所以循环数为2 .
所以我们可以知道对于环长为m的旋转来说, 顺时针旋转i格, 循环数为gcd(i,m) .
翻转 : 要分m的奇偶, 当m为奇数时, 恰好有m条对称轴经过某一点, 比如说一个三角形, 三个顶点分别为1,2,3,随便假设一点 1 在对称轴上, 那么绕这个轴翻转, (2,3)(1)就是两个循环节. 所以一条就可以产生m/2+1的循环数. 所以一共是m*(m/2+1).
当m为偶数时, 比如说正方形, 1,2,3,4, 那么对称轴就要两种情况, 第一种对角线, (假设是1,3)那么就会产生(2,4)(1)(3)的循环数, 一共两条, 即m/2*(m/2+1),如果是以对边的中点连线作为对称轴, 则会产生(1,2)(3,4)的循环数, 还是有2条, 即m/2*(m/2).
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int mod = 1e9+7; LL qsm(LL a, LL n){ LL res = 1; while(n){ if(n&1) res = res*a%mod; a = a*a%mod; n >>= 1; } return res; } int main() { int n, m, ks = 0, T; scanf("%d", &T); while(T--) { scanf("%d %d", &n,&m); LL ans = 0; for(int i=1; i<=m; i++) ans += qsm(n, __gcd(i,m)); if(m%2) ans += m*qsm(n,m/2+1); else ans += m/2*qsm(n,m/2)+m/2*qsm(n,m/2+1); ans = ans%mod*qsm(2*m,mod-2)%mod; printf("Case #%d: %lld\n", ++ks, ans); } return 0; }
8,动态树。
http://www.cnblogs.com/spfa/p/7475211.html
http://www.cnblogs.com/spfa/p/7475211.html
9,主席树 树上第K大,带修改
#include <bits/stdc++.h> using namespace std; const int MAXN = 200010; const int M = MAXN * 40; int n,q,m,TOT; int a[MAXN], t[MAXN]; int T[M], lson[M], rson[M], c[M]; void Init_getPos() { for(int i = 1; i <= n;i++) t[i] = a[i]; sort(t+1,t+1+n); m = unique(t+1,t+n+1)-t-1; } int build(int l,int r) { int root = TOT++; c[root] = 0; if(l != r) { int mid = (l+r)>>1; lson[root] = build(l,mid); rson[root] = build(mid+1,r); } return root; } int getPos(int x) { return lower_bound(t+1,t+1+m,x) - t; } int update(int root,int pos,int val) { int newroot = TOT++, tmp = newroot; c[newroot] = c[root] + val; int l = 1, r = m; while( l < r) { int mid = (l+r)>>1; if(pos <= mid) { lson[newroot] = TOT++; rson[newroot] = rson[root]; newroot = lson[newroot]; root = lson[root]; r = mid; } else { rson[newroot] = TOT++; lson[newroot] = lson[root]; newroot = rson[newroot]; root = rson[root]; l = mid+1; } c[newroot] = c[root] + val; } return tmp; } int query(int left_root,int right_root,int LCA,int k) { int lca_root = T[LCA]; int pos = getPos(a[LCA]); int l = 1, r = m; while(l < r) { int mid = (l+r)>>1; int tmp = c[lson[left_root]] + c[lson[right_root]] - 2*c[lson[lca_root]] + (pos >= l && pos <= mid); if(tmp >= k) { left_root = lson[left_root]; right_root = lson[right_root]; lca_root = lson[lca_root]; r = mid; } else { k -= tmp; left_root = rson[left_root]; right_root = rson[right_root]; lca_root = rson[lca_root]; l = mid + 1; } } return l; } //LCA部分 int rmq[2*MAXN];//rmq数组,就是欧拉序列对应的深度序列 struct ST { int mm[2*MAXN]; int dp[2*MAXN][20];//最小值对应的下标 void init(int n) { mm[0] = -1; for(int i = 1;i <= n;i++) { mm[i] = ((i&(i-1)) == 0)?mm[i-1]+1:mm[i-1]; dp[i][0] = i; } for(int j = 1; j <= mm[n];j++) for(int i = 1; i + (1<<j) - 1 <= n; i++) dp[i][j] = rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1]; } int query(int a,int b)//查询[a,b]之间最小值的下标 { if(a > b)swap(a,b); int k = mm[b-a+1]; return rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k]; } }; //边的结构体定义 struct Edge { int to,next; }; Edge edge[MAXN*2]; int tot,head[MAXN]; int F[MAXN*2];//欧拉序列,就是dfs遍历的顺序,长度为2*n-1,下标从1开始 int P[MAXN];//P[i]表示点i在F中第一次出现的位置 int cnt; ST st; void init() { tot = 0; memset(head,-1,sizeof(head)); } void addedge(int u,int v)//加边,无向边需要加两次 { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; } void dfs(int u,int pre,int dep) { F[++cnt] = u; rmq[cnt] = dep; P[u] = cnt; for(int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if(v == pre)continue; dfs(v,u,dep+1); F[++cnt] = u; rmq[cnt] = dep; } } void LCA_init(int root,int node_num)//查询LCA前的初始化 { cnt = 0; dfs(root,root,0); st.init(2*node_num-1); } int query_lca(int u,int v)//查询u,v的lca编号 { return F[st.query(P[u],P[v])]; } void dfs_build(int u,int pre) { int pos = getPos(a[u]); T[u] = update(T[pre],pos,1); for(int i = head[u]; i != -1;i = edge[i].next) { int v = edge[i].to; if(v == pre)continue; dfs_build(v,u); } } int main() { while(scanf("%d%d",&n,&q) == 2) { for(int i = 1;i <= n;i++) scanf("%d",&a[i]); Init_getPos(); init(); TOT = 0; int u,v; for(int i = 1;i < n;i++) { scanf("%d%d",&u,&v); addedge(u,v); addedge(v,u); } LCA_init(1,n); T[n+1] = build(1,m); dfs_build(1,n+1); int k; while(q--) { scanf("%d%d%d",&u,&v,&k); printf("%d\n",t[query(T[u],T[v],query_lca(u,v),k)]); } return 0; } return 0; }
二分匹配,Hopcroft-Carp算法 O(sqrt(n)*E)
#include <bits/stdc++.h> using namespace std; const int maxn = 3010; const int inf = 0x3f3f3f3f; vector<int>G[maxn]; int uN,vN; int Mx[maxn],My[maxn]; int dx[maxn],dy[maxn]; int dis; bool used[maxn]; bool SearchP(){ queue<int>q; dis = inf; memset(dx, -1, sizeof(dx)); memset(dy, -1, sizeof(dy)); for(int i=0; i<uN; i++){ if(Mx[i]==-1){ q.push(i); dx[i]=0; } } while(!q.empty()) { int u = q.front(); q.pop(); if(dx[u]>dis) break; int sz = G[u].size(); for(int i=0; i<sz; i++){ int v = G[u][i]; if(dy[v] == -1){ dy[v] = dx[u]+1; if(My[v]==-1) dis=dy[v]; else{ dx[My[v]] = dy[v]+1; q.push(My[v]); } } } } return dis!=inf; } bool DFS(int u){ int sz = G[u].size(); for(int i=0; i<sz; i++){ int v = G[u][i]; if(!used[v]&&dy[v]==dx[u]+1){ used[v] = true; if(My[v]!=-1&&dy[v]==dis) continue; if(My[v]==-1||DFS(My[v])){ My[v]=u; Mx[u]=v; return true; } } } return false; } int MaxMatch(){ int res = 0; memset(Mx,-1,sizeof(Mx)); memset(My,-1,sizeof(My)); while(SearchP()){ memset(used,false,sizeof(used)); for(int i=0; i<uN; i++) if(Mx[i]==-1&&DFS(i)) res++; } return res; } int main() { int k; while(~scanf("%d", &uN)&&uN) { scanf("%d%d", &vN,&k); for(int i=0; i<maxn; i++) G[i].clear(); while(k--) { int i,u,v; scanf("%d%d%d", &i,&u,&v); if(u>0&&v>0) G[u].push_back(v); } printf("%d\n", MaxMatch()); } return 0; }