每道題的難度大概都在noip提升組Day2T2T3難度c++
只會模擬git
模擬寫完以後,小小加了一個優化編程
指望得分20+20數據結構
光是理解題意就花了好長時間。。。學習
而後勉勉強強算是會了暴力的寫法(複雜度\(O(N!)\))優化
不會判重,可是lyq說按照他的想法這題根本不須要判重???spa
我。。。這題完全理解錯誤了debug
四聯通是指對於每一個格子向外擴展的方向3d
而實際主角能夠每次對一個聯通塊進行操做使其變色rest
還覺得每次只能處理特定形狀的部分。。。。
GG
/* * 考慮根據題意模擬 * 對每次詢問都進行[l,r]內的mod操做,取mod後最大值 * 大概有20分 */ #include <cstdio> #include <algorithm> inline int read() { int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w; } inline int max(int x,int y) {return x>y?x:y;} const int N=1e5+1; int n,m,a[N];//,ans[N]; /*struct Query{ int l,r,k,id; bool operator<(const Query &x)const {return k<x.k;} }que[N];*/ int main() { freopen("flower.in","r",stdin); freopen("flower.out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;++i) a[i]=read(); for(int ans,l,r,k,i=1;i<=m;++i) { ans=0; l=read(),r=read(),k=read(); for(;l<=r;++l) ans=max(ans,(a[l]>=k?a[l]%k:a[l])); printf("%d\n",ans); } fclose(stdin);fclose(stdout); return 0; }
/* * 嘗試理解題意 * 。。。 * 理解無能。。。 * * 等會好像是dfs的方式 * 判重不會搞。 * 那就不搞了. */ #include <stack> #include <cstdio> inline int read() { int n=0,w=1;register char c=getchar(); while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar(); return n*w; } const int N=6001,mod=1e9+7; int n,ans,x[N],y[N],lx[N],topx,ly[N],topy; void dfs() { for(int i=1;i<=n;++i) if(y[i]<ly[topy] && (x[i]<lx[topx] && x[i]>lx[topx-1]) || (x[i]>lx[topx] && x[i]<lx[topx-1])) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfs(); --topx,--topy; } ++ans; if(ans==mod)ans=0; } void dfsbegin() { for(int i=1;i<=n;++i) if(y[i]<ly[topy]) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfs(); --topx,--topy; } ++ans; } int main() { freopen("refract.in","r",stdin); freopen("refract.out","w",stdout); n=read(); for(int i=1;i<=n;++i) x[i]=read(),y[i]=read(); for(int i=1;i<=n;++i) { ly[++topy]=y[i]; lx[++topx]=x[i]; dfsbegin(); --topx,--topy; } printf("%d",ans); fclose(stdin);fclose(stdout); return 0; }
/* * 誰能告訴我樣例是怎麼出來的。。。 */ #include <cstdio> #include <cstring> const int N=51,dx[]={0,1,0,-1},dy[]={1,0,-1,0}; int r,c,ans,sum,color[N][N]; bool map[N][N]; void dfs(int x,int y) { color[x][y]=sum; for(int xx,yy,i=0;i<4;++i) { xx=x+dx[i],yy=y+dy[i]; if(xx<1 || xx>r || yy<1 || yy<c)continue; dfs(xx,yy); } } void work() { for(int i=1;i<=r;++i) for(int j=1;j<=c;++j) if(true); } int main() { freopen("paint.in","r",stdin); freopen("paint.out","w",stdout); scanf("%d%d",&r,&c); int tot=0; for(int x,i=1;i<=r;++i) for(int j=1;j<=c;++j) { scanf("%d",x); map[i][j]=x; if(!x)++tot; } /*if(r<=15 && c<=15) { for(int i=1;i<=r;++i) for(int j=1;j<=c;++j) if(!color[i][j]) { ++sum; dfs(i,j); } } else*/ if(r==1) { for(int i=1;i<=c;++i) if(map[1][i]!=map[1][i-1] && map[1][i]!=map[1][i+1]) map[1][i]=map[1][i-1],++ans; for(int i=1;i<=c;++i) if(map[1][i]) ++++i,++ans; printf("%d",ans); } else printf("%d",tot>4?tot>>1:tot); fclose(stdin);fclose(stdout); return 0; }
\(20\)分\(n^2\)作法
\(40\)分塊
另外\(20\)分的作法求前綴和,查看某個數字是否存在
當\(k\)比較大的時候就主席樹,當\(k\)比較小的時候由於主席樹複雜度會爆,用上面另\(20\)分作法
真·正解:
考慮當\(k\)肯定的時候如何求解,顯然對於全部形如\([a_k,(a+1)k)\)的值域,最大值必定是最優的
進一步觀察發現,這樣的區間總數只有\(k\times \ln k\)個,考慮分塊,那麼咱們能夠在\(O(n+k\ln k)\)的時間複雜度內處理出一個塊對於任意\(k\)的答案,詢問的時候複雜度是\(O(mS),(S\text{是塊的大小})\)的,取\(S=\sqrt{k\ln k}\)能夠達到最優複雜度\(O(n\sqrt{k\ln k})\)
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int B = 1000; const int N = 100000; int n, q; int a[N + 5]; int lst[N + 5]; int ans[105][N + 5]; int main() { //freopen("flower.in", "r", stdin); //freopen("flower.out", "w", stdout); read(n); read(q); for(int i = 0; i < n; ++i) read(a[i]); int blks = (n-1) / B + 1; for(int i = 0; i < blks; ++i) { memset(lst, 0, sizeof lst); for(int j = i * B; j < (i+1) * B && j < n; ++j) lst[a[j]] = a[j]; for(int j = 1; j <= N; ++j) chkmax(lst[j], lst[j-1]); for(int j = 1; j <= N; ++j) for(int k = 0; k <= N; k += j) chkmax(ans[i][j], lst[std::min(k + j - 1, N)] - k); } while(q--) { static int l, r, k; read(l), read(r), read(k); -- l, -- r; int x = (l / B) + 1, y = (r / B), res = 0; if(x == y + 1) { for(int i = l; i <= r; ++i) chkmax(res, a[i] % k); } else { for(int i = x; i < y; ++i) chkmax(res, ans[i][k]); for(int i = l; i < x*B; ++i) chkmax(res, a[i] % k); for(int i = r; i >=y*B; --i) chkmax(res, a[i] % k); } printf("%d\n", res); } return 0; }
10分和20分其實都是\(n^3\)作法,區別僅在於常數問題
50分是\(n^2\)的\(dp\),可是由於空間開不下就變成了\(50\)分
100分:上面50分的dp把空間卡成\(\frac{n^2}{2}\)就是100分了
\(dp[i][j]\)表示上一條折線是\(i\rightarrow j\)的
具體地講:
若將全部點按照\(y_i\)的順序進行轉移,有上界和下界兩個限制,優化比較難
那麼考慮按照\(x_i\)排序進行轉移,而且記錄\(f_{i,0/1}\)表示以第\(i\)個點爲頂端接下來向左或者向右的折線方案數,從左到右加點
考慮前\(i\)個點構成的包含\(i\)點的折線,因爲新加入的點橫座標是當前考慮的點鐘橫座標最大的,因此只多是折線的起始點或者第二個點
如圖,假設新加入點\(i\)是折線的起始點,\(k\)是一個符合轉移條件的點(也就是在\(i\)點下方),顯然可行
如圖,\(i\)是當前點,\(j\)是上一個點,\(k\)是一個符合條件的點(在\(i\)點左下方,同時在\(j\)點右下方)
那麼有
第二種狀況能夠進行前綴和優化,複雜度\(O(n^2)\)
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int N = 6000; const int mo = 1e9 + 7; pii p[N + 5]; int dp[N + 5][2], n; int main() { //freopen("refract.in", "r", stdin); //freopen("refract.out", "w", stdout); read(n); for(int i = 1; i <= n; i++) { read(p[i].fst), read(p[i].snd); } sort(p + 1, p + n + 1); for(int i = 1; i <= n; i++) { dp[i][0] = dp[i][1] = 1; for(int j = i-1; j >= 1; j--) { if(p[j].snd > p[i].snd) { (dp[j][1] += dp[i][0]) %= mo; }else { (dp[i][0] += dp[j][1]) %= mo; } } } int ans = mo - n; for(int i = 1; i <= n; i++) ans = ((ans + dp[i][0]) % mo + dp[i][1]) % mo; printf("%d\n", ans); return 0; }
7分輸出黑色聯通塊個數
26分狀壓DP
民間100分作法:每一個聯通塊和它相鄰的聯通塊連邊,最後發現是一個圖,若是每一個聯通塊只能在圖中出現一次,就會變成一棵樹
而後找最長鏈
正解作法:
能夠發現一個「比較顯然」的結論:存在一種最優方案使得每次操做的區域是上一次的子集且顏色和上一次相反
考慮概括法證實:
那麼咱們能夠枚舉最後被修改的區域,這時答案就是將同色邊邊權當成\(0\),異色邊邊權爲\(1\),然後舉例這個點最遠的黑色點的距離,對全部點取最小值便可
#include <bits/stdc++.h> using std::pair; using std::vector; using std::string; typedef long long ll; typedef pair<int, int> pii; #define fst first #define snd second #define pb(a) push_back(a) #define mp(a, b) std::make_pair(a, b) #define debug(...) fprintf(stderr, __VA_ARGS__) template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; } template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; } const int oo = 0x3f3f3f3f; string procStatus() { std::ifstream t("/proc/self/status"); return string(std::istreambuf_iterator<char>(t),std::istreambuf_iterator<char>()); } template <typename T> T read(T& x) { int f = 1; x = 0; char ch = getchar(); for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; return x *= f; } const int N = 50; int n, m; char g[N + 5][N + 5]; int dis[N + 5][N + 5]; const int dx[] = { 1, 0, -1, 0 }; const int dy[] = { 0, 1, 0, -1 }; int bfs(int x, int y) { std::deque<pii> q; memset(dis, -1, sizeof dis); dis[x][y] = 0; q.push_back(mp(x, y)); int res = 0; while(!q.empty()) { int cx = q.front().fst, cy = q.front().snd; if(g[cx][cy] == '1') chkmax(res, dis[cx][cy]); q.pop_front(); for(int i = 0; i < 4; ++i) { int nx = cx + dx[i], ny = cy + dy[i]; if(nx >= 0 && nx < n && ny >= 0 && ny < m && dis[nx][ny] == -1) { if(g[nx][ny] == g[cx][cy]) { dis[nx][ny] = dis[cx][cy]; q.push_front(mp(nx, ny)); } else { dis[nx][ny] = dis[cx][cy] + 1; q.push_back(mp(nx, ny)); } } } } return res; } int main() { //freopen("paint.in", "r", stdin); //freopen("paint.out", "w", stdout); read(n), read(m); for(int i = 0; i < n; ++i) { scanf("%s", g[i]); } int ans = oo; for(int i = 0; i < n; ++i) { for(int j = 0; j < m; ++j) { chkmin(ans, bfs(i, j)); } } printf("%d\n", ans + 1); return 0; }
平面上有一個點光源,以每秒一個單位的速度從\((x_0,y)\)沿直線走到\((x_1,y),y\lt 0\),\(x\)軸上面有\(n\)條線段會遮擋光線,\(x\)軸上方有\(q\)個點。問每一個點可以被光線直射的總時間
\(n,q\le 10^5\)
解:
反過來理解,把\(x\)軸上方的\(q\)個點看作點光源,下面移動的是接收器
那麼要求的就是對於每一個點光源,其能照射到接收器的總時間
那麼每一個點光源向周圍發射的光通過\(x\)軸上面的線段遮擋以後到下面就造成了兩個三角形(點光源和光在\(x\)軸上照亮的區間、點光源和光在移動路徑上照亮的區間)
由於移動路徑和\(x\)軸是平行的
考慮根據類似,先求出每一個點在\(x\)軸上照亮的區間,再根據比值就求出了在移動路徑上的長度
另外因爲可能會出現:接收器並無徹底通過照亮的區間就中止移動了或者接收器開始移動的位置以前的部分也可以被照亮
因此對這兩個區間(包含起點和終點的)進行二分,找到不合法(被照亮可是接收器沒有通過這裏)的區間,減掉這部分
用二分的緣由:精度差小
給出一個長度爲\(n\)的序列\(\{a_i\}\),如今進行\(m\)次操做,每次操做會修改某些\(a_i\)的值,在每次操做完成以後須要判斷是否存在一個位置\(i\)知足\(a_i=\sum_{j=1}^{i-1}a_j\)並求出任意一個\(i\)
\(n,m\le 10^5,0\le a_i\le 10^9\)
解:
若是是單點修改的話,考慮維護式子\(a_i=\sum_{j=1}^{i-1}a_j\),變成\(a_i-\sum_{j=1}^{i-1}a_j=0\),顯然對於單點修改的狀況下這個很是好維護,直接查詢是否是\(0\)就好了
區間修改的話,首先考慮一個暴力,每次從\(i=1\)開始統計\(\sum_i a_i\)而後判斷是否合法,顯然\(sum_i\)是單調不降的,符合條件的\(a_i\)必定\(\ge sum_i\)每次用線段樹找到大於等於當前前綴和的最左邊的\(a_i\)而且判斷是否合法
由於「若是一個新的\(a_i\)不合法,那麼當前前綴和至少會達到上一次的兩倍」,那麼這樣查找的複雜度就是\(O(\log\omega)\),\((\omega\)是前綴和的最大值\()\),那麼總複雜度就是\(O(m\log n\log\omega)\)
有一棵\(n\)個點的樹,每條邊有兩個參數\(a_i,b_i\),在某一時刻\(t\),一條邊的長度爲\(a_i\times t+b_i\)
問在不一樣時刻\(t=0,1,2\cdots,m-1\)這些時刻樹的直徑
\(n,m,a_i\le 10^5,b_i\le 10^9\)
解:
考慮已經獲得若干條路徑後如何計算答案, 將每條路徑表示成數對\((a_i,b_i)\), 從小到大枚舉 \(t\) , 直徑的 \(a_i\) 必定單調不降, 而且變化過程能夠用 斜率優化維護.
\(a_i\times t+b_i\ge a_j\times t+b_j\rightarrow (a_i-a_j)\times t\ge b_j-b_i\)
\(-t\le \frac{b_i-b_j}{a_i-a_j}\)
發現是一個相似上凸殼的轉移
可是這樣的複雜度是\(O(n^2)\),複雜度依然是錯的
顯然咱們須要的複雜度是\(O(n\sqrt n)\)或者\(O(n\log n)\)
問題變成如何減少路徑數量, 比較容易想到的方法是樹分治
因爲跨越中心的兩部分須要合併, 咱們採用邊分治
跨越中心的部分就是兩個部分的點的閔科夫斯基和.
$Minkowski Sum $
定義: 給出兩個點集\(A,B\), 求點集$ C ={a+b | a\in A,b\in B}$的凸包.
解法: \(C\)中凸包上的點由\(A,B\)凸包上的點相加獲得, 先求出\(A,B\)的凸包. 找到\(A,B\)一組相加後在凸包上的點, 向後按照相鄰向量的極角順序合併.
給一個初始爲空的整點集, 如今有\(n\)次操做:
- 1 x y向點集中插入\((x,y)\)
- 2 x y從點集中刪除\((x,y)\)
- 3 x y詢問至少須要添加多少個點知足這個點集造成的圖像關於\((0,0),(x,y)\)的連線對稱
\(n\le 2\times 10^5,0\le x,y\le 10^5\)
解:
注意到互相對稱的兩點到達原點的距離相同,即互相對稱的兩點在同一個圓的圓周上
由於這些點對都是整數,而圓上整數點的規模能夠看作\(\sqrt n\)(實際遠小於\(\sqrt n\),詳細可見《隱藏在素數規律中的\(\pi\)》)
對於每一個圓,維護其邊界上點的集合,每加入或者刪除一個點就計算 這個點和其它點的對稱中心並統計答案
複雜度能夠認爲是\(O(n\sqrt n\log n)\)可是難以達到理論上界
給出三個數\(u,v,p\),能夠對\(u\)進行以下操做
- \(u\leftarrow u-1\mod p\)
- \(u\leftarrow u+1\mod p\)
- \(u\leftarrow u^{p-2}\mod p\)
求一種不超過\(200\)步的方案使得\(u\)最終變成\(v\)
\(0\le u,v\lt p\le 10^9\),\(p\)是質數
解:
操做\(3\)也就是費馬小定理求逆元
由於操做\(3\)的存在, 咱們能夠近似地認爲這張圖是隨機的, 可是直接搜的指望複雜度仍是很大
考慮雙向\(BFS\),先從\(u\)出發進行\(BFS\)並取出前\(\sqrt p\)個狀態的值,顯然這部分路徑的長度不會超過\(100\)
接下來從\(v\)出發進行\(BFS\)而且在以前的答案中查詢,根據生日悖論,\(\sqrt p\)次找不到解的機率接近\(0\)
生日悖論
一個班級,\(30\)我的中,有兩人生日相同的機率接近\(100%\)
證實:
考慮全部人中沒有人的生日相同的機率
第一我的與第二我的生日不一樣的機率爲\(\frac{364}{365}\),第三我的不與前兩人生日相同的機率爲\(\frac{363}{365}\),第四人\(\cdots\),最後當人數爲\(30\)的時候,可得三十我的生日兩兩不一樣的機率=\(1\times \frac{364}{365}\times \frac{363}{365}\times \cdots\frac{335}{365}\)
這個值通過收斂,很是接近\(0\)
由於"全部人中沒有人生日相同"是"有至少兩人生日相同"的對立事件
因此「至少兩人生日相同「這一事件的機率是接近\(100%\)的
給出一個長度爲\(n\)的序列\(x\),每一個位置有\(a_i,b_i\)兩個參數,\(b_i\)互不相同,能夠進行任意次以下操做:
- 若存在\(j\ne i\)知足\(a_j=a_i\),則能夠花費\(b_i\)的代價讓\(a_i+1\)
- 若存在\(j\)知足\(a_j+1=a_i\),則能夠花費\(-b_i\)的代價讓\(a_i-1\)
定義一個序列的權值爲將序列中全部\(a_i\)變得互不相同所需的最小代價。
問給定序列的每個前綴的權值
\(n,a_i\le 2\times 10^5,1\le b_i\le n\)
解:
先考慮全部\(a_i\)互不相同的時候怎麼作,若存在\(a_i+1=a_j\),則能夠花費\(b_i-b_j\)的代價交換兩個\(a_i\),顯然最優方案會將序列中全部\(a_i\)連續的自斷操做成按\(b_i\)降序的
若是有\(a_i\)相同,則能夠先將全部\(a_i\)編程互不相同的再進行排序,可是這時候可能會擴大值域使得本來不連續的兩個區間合併到一塊兒,因而咱們須要維護一個支持合併的數據結構
嘗試用並查集維護每一個值域連續的塊,而且在每一個並查集的根上維護一個以\(b\)爲關鍵字的值域線段樹,每次合併兩個聯通塊的時候,合併他們對應的線段樹便可維護答案
有兩個串\(S,T\)和一個數字\(k\),能夠將\(S\)中任意兩個長度爲\(k\)的不相交子串取出,而且按照順序拼接到一塊兒,求一種取串的方案使得\(T\)是這個新串的子串
\(|T|\le 2k\le |S|\le 5\times 10^5\)
解:
\(T\)必定存在某個分段點使得這個點的左邊部分和右邊部分分別是兩個不相交子串的一個後綴和前綴,考慮枚舉這個分段點,那麼顯然只須要知足左邊部分第一次出現的標號小魚右邊部分最後一次出現的標號便可
進一步觀察發現,對應\(T\)的全部前綴,第一次出現的位置是單調的,咱們只須要作一次\(KMP\)便可獲得美國前綴出現的最先的位置