突然發現,做TC的500就像終場前的絕殺, 雖然難,但是還是有可能的,但是1000pt,尼瑪幾乎就是35秒13分啊!
500pt :
題意:給你一個字串由I O ? 三種字元構成,現在讓你將? 變成 I 或者 O,使得原序列存在至少一種這樣子的情況:兩個I關於一個O對稱。
花了十幾秒時間想了下直接算行不行,發現這是NP啊。。果斷反過來想,YY良久,還是沒結果,,看了題解後陷入了深深的反思,其實只要我嘗試著畫一畫非法的字串長什麼樣子,肯定能獨立搞出來的,還是沒經驗 啊。。
解法:假設就給你若干個“?”,只要你在紙上寫寫非法的字串長什麼樣子,這個問題其實就解決了。
再一次陷入沉思+反思。。。。。
int p[2550];int IOIString :: countIOIs(vector <string> mask){ string s = ""; for(int i = 0; i < mask.size(); i++) { for(int j = 0; j < mask[i].length(); j++){ s += mask[i][j]; } } int n = s.size(); p[0] = 0; for(int i = 0; i < n; i++) { p[i+1] = p[i]; if(s[i] == 'I') p[i+1] ++; } int sum = 0; if(!p[n]) sum++; for(int i = 0; i < n; i++) if(!p[i]){ if(s[i] == 'O') continue; else if(p[n]-p[i+1]==0)sum ++; for(int d = 1; d < n; d+=2) { for(int j = i+d; j < n; j+=d) { if(p[j-1 + 1] - p[j-d + 1]) break; if(s[j] == 'O') break; if(p[n] - p[j+1] == 0)sum ++; } } } int cnt = 1; for(int i = 0; i < n; i++) { if(s[i] == '?') { cnt*=2; cnt%=mod; } } return (cnt - sum + mod) % mod; } // BEGIN CUT HERE int main() { IOIString ___test; ___test.run_test(-1); } // END CUT HERE
1000pt:
題意:問你由0~9構成長度為n的非遞減序列而且能整除m的序列的個數。(n<=10^18 , m<=500).
仰慕某些能一眼看出來性質的神牛,反正我是沒看出來,看出那個關鍵的性質後,問題就是一個簡單的DP了。
關鍵性質:這樣一個非遞減的序列肯定能由小於等於9個形如(1111...111)的數構成,而且(1111.....111)(n個1)必須要存在(保證n位)。
然後考慮到數的種類太多,而且對於一個數我們只需要知道這個數對餘數的貢獻就好了。所以,我們重新分類,利用所有的數對m取餘的結果,這樣子就相當於將所有的數分成了m個剩餘類,然後我們從餘數為0的類開始取dp[i][j][k]表示前i類,取了j個數,餘數為k的總方案數。
WA兩發,預先處理的時候沒有考慮清楚整除迴圈節的情況
TLE一發:沒有預先處理逆元
總結:我真弱!
/* **********************************************Author : wuyiqiCreated Time: 2013-8-11 10:39:19File Name : final.cpp*********************************************** */#include <cstdio>#include <cstring>#include <cctype>#include <cstdlib>#include <cmath>#include <ctime>#include <iostream>#include <map>#include <set>#include <list>#include <sstream>#include <queue>#include <deque>#include <stack>#include <vector>#include <bitset>#include <algorithm>using namespace std;typedef __int64 lld;const int mod = 1000000007;//typedef long long lld;class IncreasingNumber { public: int countNumbers(long long digits, int divisor) ; }; lld cnt[510];int pos[510];int last;void init(lld n,int m){ memset(cnt,0,sizeof(cnt)); if(m==1) { last = 0; cnt[0] = n-1; return ; } int i; int pre = 0 , sum = 0; memset(pos,-1,sizeof(pos)); int rep = -1; int rec[550]; for(i = 1; i < n; i++) { sum = sum * 10 + 1; sum %= m; rec[i] = sum; if(pos[sum] != -1) { rep = i - pos[sum]; break; } pos[sum] = i; cnt[sum] ++; } if(rep == -1) { last = (sum*10+1) % m; } else { n = n - (i-1) ; lld ti = n / rep; lld left = n % rep; for(int j = i-1; j >= i-rep; j--) { cnt[rec[j]] += cnt[rec[j]] * ti; } for(int j = i-rep; j <= i-rep+left-2; j++) { cnt[rec[j]] ++; } if(left == 0) { cnt[rec[i-1]] --; last = rec[i-1]; }else last = rec[i-rep+left-1]; } }lld Pow(lld x,int y) { lld ans = 1; while(y){ if(y&1) ans=ans*x%mod; y >>= 1; x = x * x % mod; } return ans;}lld fac[10];lld CC(lld n,lld k) { if(k > n) return 0; lld ans = 1; for(int i = 1; i <= k; i++) { ans = (n-i+1)%mod * ans % mod % mod; } ans = ans * fac[k] % mod; return ans;}lld dp[510][10][510];// dp[i][j][k] 前i類(剩餘類),選了j個,餘數為kinline void Add(lld &x,lld y) { x += y; if(x >= mod) x -= mod;}int pre[510][10][510];lld gao(int m) { for(int k = 0; k < m; k++) { for(int go = 0; go <= 8; go++) { for(int i = 0; i < m; i++) { pre[k][go][i] = (k+go*i)%m; } } } fac[0] = 1; for(int i=1;i<10;i++) fac[i] = fac[i-1]*i; for(int i=1;i<10;i++) fac[i] = Pow(fac[i],mod-2); memset(dp,0,sizeof(dp)); dp[0][0][0] = 1; for(int i = 0; i < m; i++) { for(int j = 0; j <= 8; j++) { for(int k = 0; k < m; k++) if(dp[i][j][k]){ Add(dp[i+1][j][k],dp[i][j][k]); for(int go = 1; go+j <= 8; go++) if(cnt[i]){ lld tmp = dp[i][j][k] * CC(cnt[i]+go-1,go) % mod; Add(dp[i+1][go+j][pre[k][go][i]] , tmp); } } } } lld ans = 0; for(int i = 1; i <= 9; i++) { for(int j = 0; j + i <= 9; j++){ ans += dp[m][j][(m-i*last%m)%m]; ans %= mod; } } return ans;}int IncreasingNumber::countNumbers(lld n,int M) { init(n,M); return (int)gao(M);} // BEGIN CUT HERE int main() { IncreasingNumber ___test; ___test.run_test(-1); } // END CUT HERE
總結:這場的500和1000pt都是屬於觀察題(其實什麼題不是這樣子的呢QAQ),發現了關鍵性質就能AC,而且性質也並不是那麼隱晦,500分的關鍵是要動手去寫,很多人都能想到第一步,要反著計算,然後剪掉,但是在反著算那些非法串的數量的時候卻忘了去分析非法串的性質了,如上所述,只需要寫幾個非法串觀察一下性質,結果就出來了。
學會舉幾個例子,在紙上畫一畫,這一點真的很重要,分析的多了,能解決很多問題。
1000pt,典型的思維定勢了。雖然也想到了最終的解題方法肯定跟10^18扯不上關係,但是沒有抓住這種非遞減序列的特殊性,可以分拆成有限的幾個1111形式的數字。然後再利用餘數很小這一特定,劃分剩餘類,將10^18的數就分成了500類,最後再轉換成普通的dp來取就好 了。
繼續吧。