2016 Multi-University Training Contest 1
这次比赛名次算是目前来最好的一次了,总共A了6题,几乎全是dp题。真是佩服一神。
HDU5763 Another Meaning (kmp+dp)
这道题开场后五分钟一神就给我们过来讲解法,我思考了好一会儿才理解。
kmp预处理出子串p。对于父串,当枚举当i时,如果t[i]是p的后缀,那么就dp[i] = (dp[i-m]+dp[i])%mod,(m是子串的长度)。即要么和t[i]相连的串变成*(此时个数是dp[i-m]个),要么和t[i]相连的串不变,那就继承前面的dp[i-1].(前面已有dp[i] = dp[i-1]).

#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; typedef long long ll; const int maxn = 1e5+5; const ll mod = 1000000007; char p[maxn]; char t[maxn]; int n,m; ll dp[maxn]; int Next[maxn]; void getNext() { int k = -1; int j = 0; Next[0] = -1; while(j<m) { if(k == -1||p[k]==p[j]) { k++; j++; Next[j] = k; } else k = Next[k]; } } void kmp() { getNext(); int i = 0; int j = 0; dp[0] = 1; while(i<n) { if(j == -1||t[i]==p[j]) { i++; dp[i] = dp[i-1]; j++; } else j = Next[j]; if(j == m) { if(i>m) dp[i] = (dp[i]+dp[i-m])%mod; if(i==m) dp[i] = 2; j = Next[j]; } } } int main() { int T,kase = 0;cin>>T; while(T--) { scanf("%s",t); scanf("%s",p); n = strlen(t); m = strlen(p); kmp(); printf("Case #%d: ",++kase); printf("%d ",dp[n]); } return 0; }
HDU5764 After a Sleepless Night
这两天真好修炼dp,看完这题感觉和最长上升子序列有点像,就去学O(nlogn)的LIS了,老柴也套了个板子,他改的那一下子我感觉特别巧妙,真服。
对于是非0的元素,直接像普通的LIS那样处理。
对于0的元素
cnt[++len] = cnt[len-1]+1;
for(int i=len-1;i>1;i--)
{
cnt[i] = cnt[i-1]+1;
}
cnt[1] = 0;
举一个例子,比如cnt中现在是1 5 9,这时候a[i]是0,这样处理完后是,0,2,6,10.那么在下一次a[i]非0时,比如a[i]=7,那么这时候插进来,
得序列0,2,6,7;前面的那个a[i]=0仍然起了增一的作用,即相当于当时使得cnt[3] = 6,插入任何数字那个0都起了作用。

#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int maxn = 1e5+5; int a[maxn]; int cnt[maxn]; int main() { int t,n,kase = 0; scanf("%d",&t); while(t--) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int len = 1; cnt[len] = a[1]; for(int i=2;i<=n;i++) { if(a[i]) { if(a[i]>cnt[len]) { cnt[++len] = a[i]; } else { int pos = lower_bound(cnt+1,cnt+len+1,a[i])-cnt;//非递减序列中返回第一个大于等于a[i]的位置 cnt[pos] = a[i]; } } else { cnt[++len] = cnt[len-1]+1; for(int i=len-1;i>1;i--) { cnt[i] = cnt[i-1]+1; } cnt[1] = 0; } } printf("Case #%d: %d ",++kase,len); } return 0; }
这道大水题,刚开始我问老柴这题是什么意思,他说就是统计每个人名出现的次数,我说好那不就数一数就行了嘛,他说对呀,我就开始数,结果数了七八个,我就晕了,这人名,好多很难区分啊,机智的我开始用map统计了。o(^▽^)o

#include <iostream> #include <cstdio> #include <algorithm> #include <map> #include <cstring> using namespace std; map<char*,int> mp; void pre() { mp["Baltimore Bullets"] = 1; mp["Boston Celtics"] = 17; mp["Chicago Bulls"] = 6; mp["Cleveland Cavaliers"] = 1; mp["Dallas Mavericks"] = 1; mp["Detroit Pistons"] = 3; mp["Golden State Warriors"] = 2; mp["Houston Rockets"] = 2; mp["L.A. Lakers"] = 11; mp["Miami Heat"] = 3; mp["Milwaukee Bucks"] = 1; mp["Minneapolis Lakers"] = 5; mp["New York Knicks"] = 2; mp["Philadelphia 76ers"] = 2; mp["Philadelphia Warriors"] = 2; mp["Portland Trail Blazers"] = 1; mp["Rochester Royals"] = 1; mp["San Antonio Spurs"] = 5; mp["Seattle Sonics"] = 1; mp["St. Louis Hawks"] = 1; mp["Syracuse Nats"] = 1; mp["Washington Bullets"] = 1; } int main() { pre(); int t,kase = 0; scanf("%d",&t); getchar(); char ss[1004]; while(t--) { int flag = 0; gets(ss); map<char*,int>::iterator it; printf("Case #%d: ",++kase); for(it = mp.begin(); it!=mp.end();it++) { if(strcmp(it->first,ss)==0) { flag = 1; printf("%d ",it->second); break; } } if(!flag) printf("0 "); } return 0; }
一道统计逆序对的题,和曾经的线段树模板题很像,正好现在再复习下。
线段树模板:

#include <iostream> #include<cstring> #include<algorithm> #include<cstdio> using namespace std; const int maxn = 1e5+5; int a[maxn]; struct node{ int l,r,v; }; node T[maxn*4]; void build(int l,int r,int q){ T[q].l = l; T[q].r = r; if(T[q].l == T[q].r){ T[q].v = 0; return; } int mid = (l+r)/2; build(l,mid,q*2); build(mid+1,r,q*2+1); T[q].v = T[q*2].v + T[q*2+1].v; } int sum(int l,int r,int q){ if(l<=T[q].l&&T[q].r<=r){ return T[q].v; } int mid = (T[q].l+T[q].r)/2; if(r<=mid) return sum(l,r,q*2); if(l>mid) return sum(l,r,q*2+1); return sum(l,mid,q*2)+sum(mid+1,r,q*2+1); } void Add(int i,int j,int q){ if(T[q].l == T[q].r){ T[q].v += j; return; } int mid = (T[q].l+T[q].r)/2; if(i<=mid) Add(i,j,q*2); else Add(i,j,q*2+1); T[q].v = T[q*2].v+T[q*2+1].v; } void input(){ int T,n,kase = 0,x,w; cin>>T; while(T--){ scanf("%d",&n); build(1,n,1); int ans = 0; for(int i = 1; i<=n; i++) { scanf("%d",&x); w = x-i+sum(x,n,1); a[x] = max(i+w,x)-min(x,i); Add(x,1,1); } printf("Case #%d:",++kase); for(int i = 1; i<=n; i++) { printf(" %d",a[i]); } printf(" "); } } int main() { input(); return 0; }
树状数组模板:

#include <iostream> #include <cstdio> #include <algorithm> #include <map> #include <cstring> using namespace std; # include <stdio.h> # include <string.h> const int maxn = 1e5+5; int a[maxn], b[maxn], n; void u(int v){while (v) b[v]++, v -= v & -v;} int q(int v){ int x = 0; while (v <= n) x += b[v], v += v & -v; return x; } int main() { int t,kase = 0; scanf("%d",&t); while(t--) { memset (b, 0, sizeof(b)) ; scanf("%d",&n); int x,w; for(int i=1;i<=n;i++) { scanf("%d",&x); w = x-i+q(x); a[x] = max(i+w,x)-min(x,i); u(x); } printf("Case #%d:",++kase); for(int i=1;i<=n;i++) printf(" %d",a[i]); printf(" "); } return 0; }