2019-7-16考试总结
A. 礼物
概率期望。。。
对于每一个概率期望题,都可以把它看作一个有向无环图。
我们让目标状态$(11111……)$为起点,递推到起始状态$(00000……)$。
目标状态的期望步数为0。
比如,$111$ 可以 转移 $101$、$110$、$011$,
$F_{111}=F_{101} \times (1-P_2)+F_{101}\times P_2+1$,
它有$P_2$的概率转移到$111$,还有$1-P_2$的概率转移到自己。
所以$dp$式子就是 $F_s=\sum F_{s'}\times P_i+F_s\times (1-\sum P_i)$
把这个式子移项然后除过去就是答案。
基本上每一个概率期望题都是倒序$dp$,
但是也有$wd$大神用的正序。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #define Maxn 35 #define Reg register using namespace std; long long n,maxx,ans,sum,val[Maxn]; double l,sup,pv[Maxn],f[1<<20|1]; int main() { scanf("%lld",&n); for(Reg int i=1;i<=n;++i) { scanf("%lf%lld",&pv[i],&val[i]); sup+=pv[i]; sum+=val[i]; } printf("%lld\n",sum); for(Reg int i=1;i<(1<<n);++i) { l=0; for(Reg int j=1,t;j<=n;++j) { if(((1<<(j-1))&i)==0) continue; t=((1<<(j-1))^i); f[i]+=f[t]*pv[j]; l=l+pv[j]; } f[i]=(f[i]+1)/l; } printf("%0.3lf",f[(1<<n)-1]); return 0; }
B. 通讯
这个题是一个$tarjan$板子+贪心。
首先用$tarjan$缩强联通分量,之后就变成了一个有向无环图。
之后就想到最小生成树?
$No$,最小生成树只能在无向图中存在,
而这个题告诉你整个图联通,那么只需要贪心找连接到每个点的最短的边就可以。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #define Maxn 50050 #define Reg register using namespace std; long long ans; int n,m,tot,top,fic[Maxn],fat[Maxn],fir[Maxn],vis[Maxn]; int indx,cnt,low[Maxn],dfn[Maxn],stack[Maxn],ins[Maxn],pos[Maxn]; struct Tu {int st,ed,next; long long val;} lian[Maxn*10],liap[Maxn*10]; long long max(long long x,long long y) {return x>y?x:y;} long long min(long long x,long long y) {return x<y?x:y;} void add(int x,int y,long long z) { lian[++tot].st=x; lian[tot].ed=y; lian[tot].val=z; lian[tot].next=fir[x]; fir[x]=tot; return; } void adp(int x,int y,long long z) { liap[++top].st=x; liap[top].ed=y; liap[top].val=z; liap[top].next=fic[x]; fic[x]=top; return; } int get_fat(int x) { if(fat[x]==x) return x; else return fat[x]=get_fat(fat[x]); } bool comp(Tu a,Tu b) {return a.val<b.val;} long long min_tree() { long long ans=0; sort(liap+1,liap+top+1,comp); for(Reg int i=0;i<=cnt;++i) fat[i]=i; for(Reg int i=1,x,y;i<=top;++i) { if(!vis[liap[i].ed]) { vis[liap[i].ed]=1; ans+=liap[i].val; } } return ans; } void tarjan(int x) { low[x]=dfn[x]=++indx; stack[++stack[0]]=x; ins[x]=1; for(Reg int i=fir[x];i;i=lian[i].next) { if(!dfn[lian[i].ed]) { tarjan(lian[i].ed); low[x]=min(low[x],low[lian[i].ed]); } else if(ins[lian[i].ed]) low[x]=min(low[x],dfn[lian[i].ed]); } if(low[x]==dfn[x]) { ++cnt; while(stack[stack[0]]!=x) { ins[stack[stack[0]]]=0; pos[stack[stack[0]--]]=cnt; } ins[stack[stack[0]]]=0; pos[stack[stack[0]--]]=cnt; pos[x]=cnt; } return; } void init() { ans=tot=top=indx=cnt=stack[0]=0; memset(fir,0,sizeof(fir)); memset(fic,0,sizeof(fic)); for(Reg int i=0;i<=n;++i) low[i]=dfn[i]=ins[i]=pos[i]=vis[i]=0; return; } int main() { // freopen("text.in","r",stdin); while(scanf("%d%d",&n,&m)==2) { init(); if(!n&&!m) return 0; for(Reg int i=1,x,y;i<=m;++i) { long long z; scanf("%d%d%lld",&x,&y,&z); add(x,y,z); } for(Reg int i=0;i<=n;++i) if(!dfn[i]) tarjan(i); for(Reg int i=1;i<=tot;++i) { if(pos[lian[i].st]!=pos[lian[i].ed]) adp(pos[lian[i].st],pos[lian[i].ed],lian[i].val); } ans=min_tree(); printf("%lld\n",ans); } return 0; }
C. 奇袭
首先,最能想到的是$O(n^5)$的暴力,拿到$27$分,
之后用一个二维前缀和优化到$O(n^3)$,依然$27$分,
但是如果看到输入格式的最后一句话的话,可以优化到$O(n^2)$,拿到$64$分。
正解为$O(nlogn)$,$100$分
这个题正解是分治。
对于$n^2$的打法,题目说每一行每一列只能有$1$个军队,
我们把每个$y$纵坐标对应一个$x$横坐标,
把$x$递增排列。
如果一个区间合法,那么肯定满足在这个区间中$max_y-min_y=r-l$,
那么枚举每个起点和终点,判断此区间是否合法。
复杂度$O(n^2)$。
对于$O(nlogn)$的打法,
每个区间二分为 左区间$l~mid$和右区间$mid+1~r$,
那么这个区间的总方案数就是 两个分区间的方案数$+$跨$mid$的合法方案数,
两个分区间的方案数可以直接加,重点是跨$mid$的合法方案数。
对于跨$mid$的合法方案数,可以分成$4$种情况:
$1$、最大值在左,最小值在右,
$2$、最大值在右,最小值在左,
$3$、最大值和最小值在左,
$4$、最大值和最小值在右,
前两种情况,我们要满足 $max_y-min_y=r-l$,
我们对这个式子移项
第$1$种情况为 $max_l-min_r=r-l$,即 $max_l+l=min_r+r$,
还要满足 最大值在左,最小值在右,具体实现可以自己思考。
第$2$种情况类似。
后两种情况就比较简单,只要在左(或右)区间找到$max$和$min$,那么区间长度就确定,
对应的在对面的区间找到对应的位置,判断是否合法即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<queue> #include<map> #define Maxn 50050 #define INF 0x7fffffff #define New new Tree #define Reg register using namespace std; map<int,int> q; int n,maxx,minn,mup,pos[Maxn],ls1[Maxn],ls2[Maxn],vis[Maxn]; struct Tree {Tree *lch,*rch; int val;}; struct comp { bool operator () (int x,int y) {return ls1[x]>ls1[y];} }; priority_queue<int,vector<int>,comp> p; Tree *root=New(); void erfen(Tree *root,int l,int r) { if(l==r) { root->val=1; return; } int mid=(l+r)/2; if(l<=mid) { root->lch=New(); erfen(root->lch,l,mid); } if(mid+1<=r) { root->rch=New(); erfen(root->rch,mid+1,r); } root->val=root->lch->val+root->rch->val; minn=INF,maxx=-INF; for(Reg int i=mid;i>=l;--i) { minn=min(minn,pos[i]); maxx=max(maxx,pos[i]); ls1[i]=maxx,ls2[i]=minn; } minn=INF,maxx=-INF; for(Reg int i=mid+1;i<=r;++i) { minn=min(minn,pos[i]); maxx=max(maxx,pos[i]); ls1[i]=maxx,ls2[i]=minn; } minn=INF,maxx=-INF; mup=mid+1; while(!p.empty()) p.pop(); q.clear(); for(Reg int i=mid+1;i<=r;++i) { minn=min(minn,pos[i]); maxx=max(maxx,pos[i]); for(Reg int j=mup-1;j>=l;--j) { if(ls2[j]>minn) { mup=j; p.push(j); if(!q[ls1[j]+j]) q[ls1[j]+j]=1; else ++q[ls1[j]+j]; } else break; } while(!p.empty()&&ls1[p.top()]<maxx) { --q[ls1[p.top()]+p.top()]; p.pop(); } if(q[minn+i]) root->val+=q[minn+i]; } minn=INF,maxx=-INF; while(!p.empty()) p.pop(); q.clear(); mup=mid; for(Reg int i=mid;i>=l;--i) { minn=min(minn,pos[i]); maxx=max(maxx,pos[i]); for(Reg int j=mup+1;j<=r;++j) { if(ls2[j]>minn) { mup=j; p.push(j); if(!q[ls1[j]-j]) q[ls1[j]-j]=1; else ++q[ls1[j]-j]; } else break; } while(!p.empty()&&ls1[p.top()]<maxx) { --q[ls1[p.top()]-p.top()]; p.pop(); } if(q[minn-i]) root->val+=q[minn-i]; } minn=INF,maxx=-INF; for(Reg int i=mid,k,ed;i>=l;--i) { maxx=max(maxx,pos[i]); minn=min(minn,pos[i]); k=maxx-minn+1; ed=i+k-1; if(ed<mid+1||ed>r) continue; if(ls1[ed]>maxx||ls2[ed]<minn) continue; ++root->val; } minn=INF,maxx=-INF; for(Reg int i=mid+1,k,ed;i<=r;++i) { maxx=max(maxx,pos[i]); minn=min(minn,pos[i]); k=maxx-minn+1; ed=i-k+1; if(ed>mid||ed<l) continue; if(ls1[ed]>maxx||ls2[ed]<minn) continue; ++root->val; } return; } int main() { scanf("%d",&n); for(Reg int i=1,x,y;i<=n;++i) { scanf("%d%d",&x,&y); pos[x]=y; } erfen(root,1,n); printf("%d",root->val); return 0; }
总结
首先看了$T1$,看了大概$10$分钟,跳到了第$2$题,发现$T2$是个板子,
赶紧码完$T2$,打完$tarjan$,最后就是如何处理跑遍全图路径最短,
首先想到了最小生成树,模了几个样例发现都过得去,就打了一遍克鲁斯卡尔,
考试之后才发现这最小生成树只能在无向图中用,$T2$只拿了$10$分。
读了$T3$一遍题,码了一遍$n^5$的暴力,这个东西可以用二维前缀和维护,
从$n^5$降到了$n^3$。应该能拿$30%$的分
没有看到输入格式的最后一句话……
最后回去想了一下$T1$,概率期望。。
感觉这东西一定是状压,但是不怎么会转移,惊喜地看到数据范围,有$10$分的送分点。
所以最后 $10+10+27=47$。