1. 程式人生 > >寒武紀camp Day2

寒武紀camp Day2

printf 一個人 true pop gpo 研究 con ron 條件

補題進度: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];
6 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 }
View Code

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