寒武紀camp Day2
補題進度:8/10
A(計數+BIT)
題意:
給一個長度為n的數組a[],任意選0<=i<=j<n,將a[i]~a[j]從小到大排序,形成新的數組。問有多少個不同的新數組。
N,a[i]<=1000000
分析:
對答案有貢獻的ij一定是a[i]不是i~j的最小值,a[j]不是i~j的最大值,於是我們就去統計這樣區間的數量
我們用單調棧搞出r[i]表示i右邊第一個比a[i]小的位置,l[i]表示i左邊第一個比a[i]大的位置
枚舉區間左端點,那麽可行的右區間在[r[i],n]中,然後我們只需要統計其中有多少位置的l[j]>=i就行了
這個可以離線+BIT解決
B(並查集維護基環外向樹森林)
題意:
一個長度為n的01數組,最初都是0,有m個顏色,每個位置有兩種染色選擇,每種顏色只可以染一次。現在你需要找出一個染色方案滿足:
1、被染色的位置數量盡可能多
2、在1的前提下,若用1表示該位置被染,用2表示該位置沒被染,你需要讓這個字典序盡量大
3、在2的前提下,一個位置如果被染那麽它要麽是1要麽是2(表示染了第一種備選顏色還是第二種),你需要讓這個字典序盡量小
n,m<=5*10^5
分析:
如果是問最多染色的個數,那顯然只需要維護一下基環外向樹森林就行了
現在要輸出方案,我們發現第2個條件其實就是讓我們盡可能優先用先讀入的邊,這個在我們維護基環外向樹森林的時候就成功處理了
第三個條件其實是讓我們考慮最後的基環樹/樹的染色分配
首先對於基環樹,只有環上的有兩種選擇,樹邊是定死的,我們只需要枚舉兩種情況即可
對於樹,我們貪心來看,取出id最小的邊,令其為1,按照方向去dfs塗色,再取出id次小的邊,以此類推……
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e6,inf=1e7; 4 int ans[maxn+5],l[maxn+5],r[maxn+5]; 5 int f[maxn+5];View Code6 bool c[maxn+5],incircle[maxn+5]; 7 bool vis[maxn+5]; 8 int n,m,len,top; 9 vector<int> circle,E; 10 struct edge 11 { 12 int from,to,id; 13 }e[maxn*2+5]; 14 int head[maxn+5],nx[maxn+5]; 15 int s[maxn+5]; 16 bool use[maxn*2+5]; 17 void addedge(int u,int v,int id) 18 { 19 ++len; 20 e[len]={u,v,id}; 21 nx[len]=head[u]; 22 head[u]=len; 23 ++len; 24 e[len]={v,u,id}; 25 nx[len]=head[v]; 26 head[v]=len; 27 } 28 int find(int x) 29 { 30 if(f[x]==x) return x;else return f[x]=find(f[x]); 31 } 32 void dfs(int k) 33 { 34 if(vis[k]) 35 { 36 int now=top; 37 circle.push_back(s[now]); 38 --now; 39 while(now>=1&&e[s[now]].to!=k) circle.push_back(s[now--]); 40 return; 41 } 42 vis[k]=1; 43 for(int i=head[k];i!=-1;i=nx[i]) 44 { 45 if(use[i]) continue; 46 edge u=e[i]; 47 s[++top]=i; 48 use[i]=use[i^1]=1; 49 dfs(u.to); 50 --top; 51 } 52 } 53 void paint(int k,int fa) 54 { 55 for(int i=head[k];i!=-1;i=nx[i]) 56 { 57 edge u=e[i]; 58 if(u.to==fa||incircle[u.to]) continue; 59 if(k==l[u.id]) ans[u.id]=2;else ans[u.id]=1; 60 paint(u.to,k); 61 } 62 } 63 void workcircle() 64 { 65 for(int i=0;i<circle.size();++i) incircle[e[circle[i]].to]=1; 66 for(int i=0;i<circle.size();++i) paint(e[circle[i]].to,0); 67 int minid=n+1; 68 for(int i=0;i<circle.size();++i) 69 { 70 edge u=e[circle[i]]; 71 if(u.to==l[u.id]) ans[u.id]=1;else ans[u.id]=2; 72 minid=min(minid,u.id); 73 } 74 if(ans[minid]==2) 75 for(int i=0;i<circle.size();++i) ans[e[circle[i]].id]=3-ans[e[circle[i]].id]; 76 } 77 void dfs1(int k,int fa) 78 { 79 for(int i=head[k];i!=-1;i=nx[i]) 80 { 81 edge u=e[i]; 82 if(u.to==fa) continue; 83 E.push_back(i); 84 dfs1(u.to,k); 85 } 86 } 87 bool cmp(const int x,const int y) 88 { 89 return e[x].id<e[y].id; 90 } 91 void worktree(int start) 92 { 93 E.clear(); 94 dfs1(start,0); 95 sort(E.begin(),E.end(),cmp); 96 for(int i=0;i<E.size();++i) 97 { 98 edge u=e[E[i]]; 99 if(ans[u.id]) continue; 100 ans[u.id]=1; 101 if(u.to==l[u.id]) paint(u.to,u.from);else paint(u.from,u.to); 102 } 103 } 104 int main() 105 { 106 int size = 256 << 20; // 256MB 107 char *p = (char*)malloc(size) + size; 108 __asm__("movl %0, %%esp\n" :: "r"(p)); 109 freopen("ce.in","r",stdin); 110 freopen("ce.out","w",stdout); 111 scanf("%d%d",&n,&m); 112 for(int i=1;i<=m;++i) f[i]=i,head[i]=-1; 113 len=-1; 114 for(int i=1;i<=n;++i) 115 { 116 scanf("%d%d",&l[i],&r[i]); 117 int u=find(l[i]),v=find(r[i]); 118 if(c[u]&&c[v]) continue; 119 if(u!=v) f[u]=v,c[v]|=c[u]; 120 else 121 if(!c[u]&&!c[v]) f[u]=v,c[v]=1; 122 else continue; 123 addedge(l[i],r[i],i); 124 } 125 for(int i=1;i<=m;++i) 126 if(!vis[i]) 127 { 128 circle.clear(); 129 top=0; 130 dfs(i); 131 if(!circle.empty()) workcircle(); 132 else worktree(i); 133 } 134 for(int i=1;i<=n;++i) printf("%d",ans[i]); 135 return 0; 136 }
C(計數+線段樹)
題意:
給出一個長度為n的數組a[],問有多少對(x,y)滿足:
1、0<=x<=y<n
2、任意i∈[x,y] & j∉[x,y],有a[i]≠a[j]
n<=1000000
分析:
直觀來講,我們是要找區間[x,y]使得顏色被分開了
我們把同樣的顏色提取出來,那麽對於每個位置我們都能方便的求出l[i],r[i]表示a[i]這個顏色最左邊在哪,最右邊在哪
那麽x只能是一個顏色的起點,y只能是一個顏色的終點
我們去枚舉y,計算有多少個合法的x
即要滿足max{r[x],r[x+1],...,r[y]}<=y 且 min{l[x],l[x+1],...,l[y]}>=x
那麽對於每個y,我們可以用線段樹求出最左邊的x滿足max{r[x],r[x+1],...,r[y]}<=y,記為posl[y]
對於每個x,我們可以用線段樹求出最右邊的y滿足min{l[x],l[x+1],...,l[y]}>=x,即為posr[x]
那麽我需要找出[posl[y],y]裏面有多少少符合題意的x,即那些posr[x]>=y的x,一眼直接用可持久化線段樹支持詢問
但仔細分析會發現這樣的posr[x]是單減的,所以同樣只需要用線段樹找出那個分界就行了
D(組合計數)
略
E(構造)
題意:
給你一顆n個點的二叉樹,你需要給每個點重新編號1~n使得{fa[2],fa[3],fa[4],...,fa[n]}這個序列字典序最小
n<=2e5
分析:
顯然是用bfs的順序給每個點重新編號,但主要矛盾點就是某個點有兩個孩子,兩個孩子誰先編號的問題
顯然貪心選擇這兩個孩子當中size最大的那個
若這兩個孩子的size又相同呢……?
那麽再比較這兩個孩子的孩子……這樣深入下去
這樣時間復雜度能保證嗎?看似是O(n^2)的啊
每次考察一個點u,向下需要一直深入到size有分歧的時候,考慮最差情況就是完全二叉樹
完全二叉樹的情況下復雜度是O(nlogn)的,所以這個算法是work的
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=2e5; 4 vector<int>g[maxn+5]; 5 int ans[maxn+5],fa[maxn+5]; 6 int n; 7 bool check(int x,int y) 8 { 9 queue<int> p,q; 10 p.push(x); 11 q.push(y); 12 while(true) 13 { 14 if(p.empty()) return true; 15 if(q.empty()) return false; 16 x=p.front(),p.pop(); 17 y=q.front(),q.pop(); 18 if(g[x].size()!=g[y].size()) return g[x].size()<g[y].size(); 19 for(int i=0;i<g[x].size();++i) p.push(g[x][i]); 20 for(int i=0;i<g[y].size();++i) q.push(g[y][i]); 21 } 22 return true; 23 } 24 void dfs(int k) 25 { 26 for(int i=0;i<g[k].size();++i) dfs(g[k][i]); 27 if(g[k].size()==2) 28 if(check(g[k][0],g[k][1])) swap(g[k][0],g[k][1]); 29 } 30 void paint() 31 { 32 queue<int> q; 33 vector<int> a; 34 q.push(1); 35 int id=0; 36 while(!q.empty()) 37 { 38 int u=q.front(); 39 //printf("ce : %d\n",u); 40 q.pop(); 41 ++id; 42 ans[u]=id; 43 if(u!=1) a.push_back(ans[fa[u]]-1); 44 for(int i=0;i<g[u].size();++i) q.push(g[u][i]); 45 } 46 for(int i=0;i<a.size()-1;++i) printf("%d ",a[i]);printf("%d",a[a.size()-1]); 47 printf("\n"); 48 } 49 int main() 50 { 51 freopen("ce.in","r",stdin); 52 int T; 53 scanf("%d",&T); 54 while(T--) 55 { 56 scanf("%d",&n); 57 for(int i=0;i<=n;++i) g[i].clear(); 58 for(int i=2;i<=n;++i) 59 { 60 int x; 61 scanf("%d",&x); 62 ++x; 63 g[x].push_back(i); 64 fa[i]=x; 65 } 66 dfs(1); 67 //printf("ok\n"); 68 paint(); 69 } 70 return 0; 71 }View Code
F
待填坑
G(模擬)
題意:
在二維平面上有n個點,你需要開車走折線依次通過這些點,你最初在1號點,問你最少需要轉彎多少次才能依次經過這n個點?
n<=1000
分析:
樣例比較良心
我們把所有點按照順序連一下,發現只有出現下面這個情形的時候我們才會做更改
那麽我們只需要從前到後去看有多少個這個結構就行了
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e3; 4 struct Point 5 { 6 int x,y; 7 Point() 8 { 9 10 } 11 Point(int _x,int _y) 12 { 13 x=_x,y=_y; 14 } 15 Point operator - (const Point &a) const 16 { 17 return Point(x-a.x,y-a.y); 18 } 19 }p[maxn+5]; 20 bool flag[maxn+5]; 21 int n,top,ans; 22 int cross(Point a,Point b) 23 { 24 return a.x*b.y-a.y*b.x; 25 } 26 bool pall(Point a,Point b) 27 { 28 if(cross(a,b)!=0) return false; 29 return a.x*b.x+a.y*b.y>0; 30 } 31 int main() 32 { 33 freopen("ce.in","r",stdin); 34 //freopen("ce.out","w",stdout); 35 int T; 36 scanf("%d",&T); 37 while(T--) 38 { 39 scanf("%d",&n); 40 for(int i=1;i<=n;++i) scanf("%d%d",&p[i].x,&p[i].y); 41 for(int i=0;i<=n;++i) flag[i]=0; 42 if(n<=2) 43 { 44 printf("0\n"); 45 continue; 46 } 47 ans=0; 48 for(int i=3;i<=n;++i) 49 { 50 if(!pall(p[i]-p[i-1],p[i-1]-p[i-2])) ++ans; 51 //printf("%d\n",ans); 52 if(i!=3) 53 if(!flag[i-1]&&!flag[i-2]&&1LL*cross(p[i-1]-p[i-2],p[i]-p[i-1])*cross(p[i-2]-p[i-3],p[i-1]-p[i-2])>0&&cross(p[i]-p[i-1],p[i-2]-p[i-3])!=0) 54 { 55 if(1LL*cross(p[i-2]-p[i-3],p[i]-p[i-1])*cross(p[i-2]-p[i-3],p[i-1]-p[i-2])>0) 56 { 57 58 59 --ans; 60 flag[i-1]=1; 61 } 62 } 63 64 } 65 printf("%d\n",ans); 66 } 67 return 0; 68 }View Code
H(狀壓dp+遞歸)
題意:
有m道題目,有n個人,用0/1表示每個人是否會做每道題。現在你需要從m道題中挑出8道題組成一場考試,n個人進行這場考試。若一個人至少做出了5題,那麽他就及格了。現在問你有多少種組題方案使得最後及格的人數在[l,r]之間並且是3的倍數。
3<=n<=500000 8<=m<=20 3<=l<=r<=n
分析:
枚舉每一種組題的方案,計算該組題情況下最後及格的人數
容易想到需要將每個人的解題狀態找一個共同的性質把其壓縮起來,才能夠減少最後的狀態數,不然對於每種組題方案都要O(n)遍歷
dp[i][s1][j][s2]表示對於前i道題,題目的選擇情況是s1,答對了j個題,且這些人後面的答題情況是s2的總人數
很明顯s1只有前i位有用,s2只有i後面的有用,於是我們把s1和s2狀態合並到一起
那麽顯然就能狀壓dp了
分析一下復雜度,是$20*2^{20}*5$的
這是有點超時的,優化常數也不能在1s內通過
研究下原因是題目限定了“選8道題”,所以由很多選題狀態都是無用的
於是我們用dfs去代替for循環枚舉,就能800ms過了
I(計數+BIT)
題意:
n<=1e6
分析:
仔細分析會發現這個上升序列和下降序列一定會交於唯一位置(枚舉這個位置給上升還是下降,會產生兩種方案)
那麽我們只需要枚舉這個交點i,然後統計這個交點是否能對答案產生貢獻
能產生貢獻那就是:i前面比a[i]小的數字呈上升趨勢,比a[i]大的數字呈下降趨勢;i後面比a[i]小的數字呈下降趨勢,比a[i]大的數字呈上升趨勢
那麽如何判斷呢?以判斷i前面比a[i]小的數字是否呈上升趨勢為例
我們只需要統計出兩個東西,一個東西是從左往右以a[i]為棧頂的單調棧的元素個數,另一個東西是1~i中<=a[i]的數字個數
第二個東西我們只需要用BIT來計算就行了
J
待填坑
寒武紀camp Day2