补题进度:8/10
A(组合计数)
题意:
一个人站在数轴原点,每秒有1/4概率向前走一步,1/4概率向后走一步,1/2概率不动,问t秒后在p位置的概率。
t,p<=100000
分析:
枚举不动的个数,于是向前走的个数和向后走的个数都确定了,然后就可组合计数了。
B(平面图k小割)
题意:
给出一个n个点的树,1是根节点,每个点有点权,输出前k小的包含1节点的连通块的权值。
n<=10^5,k<=10^5,点权<=10^9
分析:
连通块不好处理,一个连通块实际上对于一个割集,我们可以这样转化:
把1当作源点,建一个汇点,把每个叶子向汇点连边,然后树上每条边的边权表示其子树里的点权和,那么这样一个割就对应了包含S点的一个连通块的点权和,所以问题就变成了求该图的k大割
求k大割是没法求的,但注意到该问题是平面图,所以可以建出对偶图跑k短路。
对于k短路,可以用可持久化堆来实现。
1 /* 2 可持久化堆优化k短路 3 求最短路径树:O(nlogn) 4 求k短路:O(mlogm+klogk) 5 空间复杂度:O(mlogm),但具体开数组要注意常数,要看执行merge操作的次数 6 */ 7 #include<bits/stdc++.h> 8 using namespace std; 9 const int maxn=100000,maxm=200000,maxsize=3000000;//maxsize是堆的最大个数 10 const long long inf=1000000000000000LL; 11 int a[maxn+5]; 12 long long s[maxn+5]; 13 int n,k,m,len,tot; 14 int S,T; 15 vector<int> g1[maxn+5]; 16 int mi[maxn+5],mx[maxn+5]; 17 int head[maxn+5],nx[maxm+5]; 18 long long dis[maxn+5]; 19 int pos[maxn+5],rt[maxn+5]; 20 struct Edge 21 { 22 int to; 23 long long w; 24 }e[maxm+5]; 25 struct node 26 { 27 /* 28 u是当前堆顶的节点编号 29 key是当前堆顶对应边的权值,边的权值定义为:走这条边要多绕多少路 30 l,r分别是堆左右儿子的地址 31 */ 32 int u; 33 long long key; 34 int l,r; 35 }H[maxsize+5]; 36 struct heapnode 37 { 38 /* 39 求k短路时候用到的数据结构 40 len表示1~倒数第二条边的边权和 41 root表示倒数第二条边的tail的H[tail],其中堆顶就是最后一条边 42 */ 43 long long len; 44 int root; 45 bool operator < (const heapnode& x) const 46 { 47 return len+H[root].key>x.len+H[x.root].key; 48 } 49 }; 50 priority_queue<heapnode> q; 51 void addedge(int u,int v,long long w) 52 { 53 54 //printf("%d %d %lld ",u,v,w); 55 e[++len]={v,w}; 56 nx[len]=head[u]; 57 head[u]=len; 58 } 59 void dfs(int k,int fa) 60 { 61 s[k]=a[k]; 62 bool flag=0; 63 mi[k]=n+1; 64 mx[k]=0; 65 for(int i=0;i<g1[k].size();++i) 66 if(g1[k][i]!=fa) 67 { 68 flag=1; 69 dfs(g1[k][i],k); 70 s[k]+=s[g1[k][i]]; 71 mi[k]=min(mi[k],mi[g1[k][i]]); 72 mx[k]=max(mx[k],mx[g1[k][i]]); 73 } 74 if(!flag) mi[k]=mx[k]=++m; 75 } 76 int newnode(int u,long long key) 77 { 78 ++tot; 79 H[tot]={u,key,0,0}; 80 return tot; 81 } 82 int merge(int u,int v) 83 { 84 /* 85 merge两个堆u和v 86 这里是采用随机堆,方便合并,方便持久化 87 */ 88 if(!u) return v; 89 if(!v) return u; 90 if(H[v].key<H[u].key) swap(u,v); 91 int k=++tot; 92 H[k]=H[u]; 93 if(rand()%2) H[k].l=merge(H[k].l,v); 94 else H[k].r=merge(H[k].r,v); 95 return k; 96 } 97 void Kshort() 98 { 99 /* 100 求k短路 101 */ 102 dis[T]=0; 103 for(int i=0;i<T;++i) dis[i]=inf; 104 tot=0; 105 for(int i=m-1;i>=0;--i) 106 { 107 /* 108 DAG图求最短路径树 109 */ 110 int fa=0; 111 for(int j=head[i];j!=-1;j=nx[j]) 112 if(dis[i]>e[j].w+dis[e[j].to]) 113 { 114 dis[i]=e[j].w+dis[e[j].to]; 115 pos[i]=j; 116 fa=e[j].to; 117 } 118 rt[i]=rt[fa]; 119 for(int j=head[i];j!=-1;j=nx[j]) 120 if(j!=pos[i]) 121 { 122 //printf("ce : %d %d ",i,e[j].to); 123 rt[i]=merge(rt[i],newnode(e[j].to,e[j].w+dis[e[j].to]-dis[i])); 124 } 125 } 126 //printf("tot : %d ",tot); 127 //printf("len : %d ",len); 128 //printf("m : %d ",m); 129 //for(int i=0;i<=T;++i) printf("%d : %lld %d ",i,dis[i],pos[i]); 130 printf("%lld ",dis[S]+s[1]); 131 heapnode now={0LL,rt[S]}; 132 if(now.root) q.push(now); 133 while(--k&&!q.empty()) 134 { 135 /* 136 每次从优先队列队首取出最小的边集 137 每个边集对对应一种合法的k短路走法 138 有两种扩展方法 139 第一种:将最后一条边换成所在堆的次小元素(相当于从堆里把堆顶删除) 140 第二种:新加一条边,即从最后一条边继续往后走 141 */ 142 now=q.top(); 143 q.pop(); 144 printf("%lld ",now.len+H[now.root].key+dis[S]+s[1]); 145 int id=merge(H[now.root].l,H[now.root].r); 146 //printf("%d : %d %lld ",id,H[id].u,H[id].key); 147 if(id) 148 q.push({now.len,id}); 149 now.len+=H[now.root].key; 150 if(rt[H[now.root].u]) 151 q.push({now.len,rt[H[now.root].u]}); 152 } 153 } 154 int main() 155 { 156 srand(time(0)); 157 scanf("%d%d",&n,&k); 158 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 159 for(int i=1;i<n;++i) 160 { 161 int u,v; 162 scanf("%d%d",&u,&v); 163 g1[u].push_back(v); 164 g1[v].push_back(u); 165 } 166 dfs(1,0); 167 for(int i=0;i<=n+1;++i) head[i]=-1; 168 for(int i=2;i<=n;++i) addedge(mi[i]-1,mx[i],-s[i]); 169 for(int i=0;i<m;++i) addedge(i,i+1,0); 170 S=0,T=m; 171 Kshort(); 172 return 0; 173 }
C(随机算法)
题意:
给出一个n个点的树,每个点有自己的颜色,选出一个包含点数最少的连通块,使得其中有k种不同的颜色。
n<=10000,k<=5,每个点颜色<=n
分析:
如果每个点的颜色是1~5,那就很好办了,我们只需要做树形dp就可以了,dp[i][j]表示以i为根的子树,颜色包含情况为j的最少点数
那么这个dp就是n*4^5的
但现在颜色数量有很多,无法表示状态
考虑随机算法,我们将1~n颜色随机映射到1~k,我们来分析下正确的概率:
分母很明显是$k^n$
成功当且仅当作为答案的那一组颜色被染成了k种不同的颜色,所以分子就是$k!*k^{n-k}$
所以成功的概率是$frac{k^n}{k!*k^{n-k}} = 0.2$
于是随个30次就行了
D(递推)
题意:
k-left tree指的是一类树,需要满足两个性质:
1、从根向每个叶子走的过程中经过的左孩子的个数<k
2、除叶子节点外的其它节点都有2个孩子
求n个叶子节点的k-left tree有多少种
n,k<=5000
分析:
一个k-left tree的左子树一定是k-1 left tree 右子树一定是k left tree树,只需要枚举左右两边分配的叶子节点个数就行了,O(n^3)的算法是显然的。
我们通过O(n^3)的算法算出了所有的ans[1..n][1..k],已经不能再优化了,但该题只需要求固定k下的ans[1..n],所以我们只能换思路用O(n^2)的去解决。
考虑已经知道了k限制下,有n个节点的所有树,如何更新到n+1的答案
我们发现该树有n个叶子说明有n-1个非叶子,我们只需要去考虑这些非叶子的形状就行了
从n更新到n+1实际上给n对应的树里面添加一个树枝,但为了不重复计数,我们人为规定该树枝只能添加到先序遍历最后的位置(如果在中间添加,可以看成删去最后的那个树枝,保留目前这个树枝,将这个树作为n的答案)
于是dp[i][j]表示i个树枝,从根到最后一个树枝经过了j个左儿子的树的总数,那么显然对于i+1,我们可以在这j个左儿子那添加一条向右的树枝,所以可以转移到dp[i+1][j+1],dp[i+1][j],....,dp[i+1][1],前缀和优化即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=5005,Mod=1e9+9; 4 int k,n,f[N][N]; 5 void inc(int &x,long long y) 6 { 7 x=(x+y+Mod)%Mod; 8 } 9 int main() 10 { 11 12 cin>>k>>n; 13 /*for(int i=2;i<=k;++i) f[1][i]=1; 14 for(int j=3;j<=k;++j) 15 for(int i=1;i<=n;++i) 16 for(int l=1;l<i;++l) 17 inc(f[i][j],1LL*f[l][j-1]*f[i-l][j]%Mod);*/ 18 /*for(int j=1;j<=k;++j) 19 for(int i=1;i<=n;++i) 20 printf("%d %d : %d ",i,j,f[i][j]);*/ 21 k-=2; 22 f[1][1]=1; 23 f[2][1]=1; 24 for(int i=2;i<n;++i) 25 { 26 for(int j=1;j<=k;++j) 27 inc(f[i+1][min(j+1,k)],f[i][j]); 28 for(int j=k-1;j>=1;--j) inc(f[i+1][j],f[i+1][j+1]); 29 } 30 for(int i=1;i<=n;i++) 31 { 32 int ans=0; 33 for(int j=1;j<=k;++j) inc(ans,f[i][j]); 34 printf("%d ",ans); 35 } 36 return 0; 37 }
E(找规律)
略
F(Fibonacci树递推)
题意:
问深度为n的fibonacci树的白点距离情况,即相距1,2,...,,2*n的白点对分别有多少对。
n<=5000
fibonacci树指的是根是白点,一个白点下面一个黑点,一个黑点下面一个白点和一个黑点形成的树。
分析:
fibonacci树有这样的性质:每个深度的点的个数、每个深度的黑点个数、每个深度的白点个数分别都是fibonacci数列
dp[i][0/1]表示深度为i的fibonacci树,根节点是白/黑点情况下的白色点对相距情况(保存的是1~2*i的数组)
很明显dp[i][0]=dp[i-1][1],dp[i][1]=dp[i-1][0]+dp[i-1][1]+两个子树之间贡献的答案
两个子树之间的贡献的答案就是两个fibonacci的卷积,所以这样是O(n^3)
如果卷积用FFT,那么是O(n^2logn)也是不能通过的
实际上两个fibonacci的卷积可以这样做:
我们枚举a的下标j,然后将a[j]*b[1,2,...,i-1]放到对应的ans[j+1,j+2,...,j+i-1]位置上,问题就变成了给区间加上一个fibonacci数列,最终求区间的每一个位置的数字
这我们可以用类似差分的做法来O(1)修改,O(n)询问
所以时间复杂度就是降到O(n^2),但是常数有点大,大概是std的1.5倍,在某oj上不能通过。
G(可持久化线段树优化建边)
题意:
有n个人,每个人三个属性a,b,c,若一个人的某两项属性大于另外一个人的对应两项属性,那么这个人可以击败另一个人。问有多少个人可以击败其它所有人。
n<=100000
分析:
如果n比较小,那么就暴力建图然后缩点就行了。
这里需要用数据结构优化建边。
对于三个属性,我们每次提取两维考虑,假设这两维是x,y
按照x从小到大排序,然后从左往右扫,假设现在来到了第i个人,那么连边就是“向1~i-1个人中y属性小于自己的人建边”
如果只有“向y属性小于自己的人建边”,那么建一个权值线段树,把每个线段树节点作为辅助节点就行了
现在有了“向1~i-1个人中”这个限制,那也很简单,我们建可持久化线段树就行了
建可持久化线段树唯一注意的是,新点u向旧点u也要连一条边
这样边数就从O(n^2)下降到了O(nlogn),点数从O(n)上升到了O(nlogn)
然后缩点,问题变成了有一个DAG图,有一些关键节点(1~n),我们想知道这关键节点当中有哪些点可以到达其它关键节点
我们只需要对这个DAG图把所有非关键节点都拓扑掉,剩下关键节点就被剥出来了,若关键节点是唯一入度为0的节点,那么该关键节点对应强连通分量里面的所有原来的节点答案就是1,其它答案是0。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=100000,maxsize=100000*20*3,maxm=6*maxsize; 4 int a[maxn+5],b[maxn+5],c[maxn+5],id[maxn+5]; 5 int rt[maxn+5]; 6 int n,tot,num,sz,len; 7 int head[maxsize+5],head1[maxsize+5]; 8 int nx[maxm+5],e[maxm+5]; 9 int t[maxsize+5],color[maxsize+5]; 10 bool v[maxsize+5]; 11 int ans[maxsize+5],d[maxsize+5]; 12 struct tree 13 { 14 int lc,rc; 15 }T[maxsize+5]; 16 void addedge(int u,int v) 17 { 18 if(!u||!v) return; 19 //printf("%d %d ",u,v); 20 ++len; 21 e[len]=v; 22 nx[len]=head[u]; 23 head[u]=len; 24 ++len; 25 e[len]=u; 26 nx[len]=head1[v]; 27 head1[v]=len; 28 } 29 void adde(int u,int v) 30 { 31 ++len; 32 e[len]=v; 33 nx[len]=head1[u]; 34 head1[u]=len; 35 } 36 int build(int last,int l,int r,int x,int id) 37 { 38 int k=++tot; 39 T[k]=T[last]; 40 addedge(k,last); 41 if(l==r) 42 { 43 addedge(k,id); 44 return k; 45 } 46 int mid=(l+r)>>1; 47 if(x<=mid) T[k].lc=build(T[last].lc,l,mid,x,id),addedge(k,T[k].lc); 48 else T[k].rc=build(T[last].rc,mid+1,r,x,id),addedge(k,T[k].rc); 49 return k; 50 } 51 void add(int k,int l,int r,int x,int id) 52 { 53 if(!k) return; 54 if(l==r) 55 { 56 addedge(id,k); 57 return; 58 } 59 int mid=(l+r)>>1; 60 if(x<=mid) add(T[k].lc,l,mid,x,id); 61 else addedge(id,T[k].lc),add(T[k].rc,mid+1,r,x,id); 62 } 63 void work(int *x,int *y) 64 { 65 for(int i=1;i<=n;++i) id[x[i]]=i; 66 for(int i=1;i<=n;++i) 67 { 68 rt[i]=build(rt[i-1],1,n,y[id[i]],id[i]); 69 add(rt[i],1,n,y[id[i]],id[i]); 70 } 71 } 72 void dfs(int k) 73 { 74 v[k]=1; 75 for(int i=head[k];i!=-1;i=nx[i]) 76 if(!v[e[i]]) dfs(e[i]); 77 ++sz; 78 t[sz]=k; 79 } 80 void dfs1(int k) 81 { 82 v[k]=1; 83 color[k]=num; 84 for(int i=head1[k];i!=-1;i=nx[i]) 85 if(!v[e[i]]) dfs1(e[i]); 86 } 87 queue<int> q; 88 int main() 89 { 90 int size = 256 << 20; // 256MB 91 char *p = (char*)malloc(size) + size; 92 __asm__("movl %0, %%esp " :: "r"(p)); 93 scanf("%d",&n); 94 for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i],&b[i],&c[i]); 95 for(int i=0;i<=maxsize;++i) head[i]=head1[i]=-1; 96 tot=n; 97 work(a,b); 98 //printf("%d %d ",tot,len); 99 work(a,c); 100 //printf("%d %d ",tot,len); 101 work(b,c); 102 //printf("%d %d ",tot,len); 103 for(int i=1;i<=tot;++i) if(!v[i]) dfs(i); 104 memset(v,0,sizeof(v)); 105 for(int i=sz;i>=1;--i) if(!v[t[i]]) ++num,dfs1(t[i]); 106 memset(v,0,sizeof(v)); 107 for(int i=1;i<=n;++i) 108 v[color[i]]=1; 109 for(int i=0;i<=num;++i) head1[i]=-1; 110 for(int i=1;i<=tot;++i) 111 for(int j=head[i];j!=-1;j=nx[j]) 112 { 113 int u=color[i],vv=color[e[j]]; 114 if(u==vv) continue; 115 ++d[vv]; 116 adde(u,vv); 117 //printf("ok : %d ",color[vv]); 118 } 119 for(int i=1;i<=num;++i) 120 if(!d[i]&&!v[i]) q.push(i); 121 while(!q.empty()) 122 { 123 int u=q.front(); 124 q.pop(); 125 for(int i=head1[u];i!=-1;i=nx[i]) 126 { 127 int to=e[i]; 128 --d[to]; 129 if(d[to]==0&&!v[to]) q.push(to); 130 } 131 } 132 //printf("num : %d ",num); 133 //for(int i=1;i<=n;++i) printf("%d : %d ",i,color[i]); 134 int sum=0; 135 int winner=0; 136 for(int i=1;i<=num;++i) 137 if(v[i]&&d[i]==0) ++sum,winner=i; 138 if(sum==1) 139 for(int i=1;i<=n;++i) 140 if(color[i]==winner) ans[i]=1; 141 for(int i=1;i<=n;++i) printf("%d ",ans[i]); 142 //cout<<clock(); 143 return 0; 144 }
H(贪心+set)
题意:
你有n个商品,Alice买第i件商品花ai元,Bob买第i件商品花bi元,一个商品只有一个,显然一个商品不能被两人同时买,Alice可以最多买A个,Bob可以最多买B个,问他们该如何买,你才能获得最大的收益。
A+B<=n<=10^5
ai,bi可正可负
分析:
我们先把所有负权值修改为0,那么“最多买”就变成了“恰好买”
我们按照ai从大到小排个序,首先Alice买前A个,Bob买后n-A个中bi最大的B个
然后我们去枚举Alice买的最后一个商品i,那么1~i每个商品都被Alice或者Bob买了,不会有商品没有被买(假如这样,那么Alice去买那个没人买的一定比买现在的第i个要来的划算)
很显然这样的答案就是1~i里面a[i]的和加上i-A个最大的b[i]-a[i]的和(即i个商品里面Alice买A个,Bob买i-A个,Bob买的这i-A个一定是b[i]-a[i]最大的i-A个),然后Bob还可以买B-(i-A)个,就是从i+1~n里面挑b[i]最大的B-(i-A)个买,这个显然可以用两个平衡树去维护
但当然因为个数恰好是递增、递减的,所以我们也可以在i递增的过程中用两个set来维护
时间复杂度O(nlogn)
I(推导+容斥计算)
待填坑
K(推导+杜教筛)
待填坑