有一些题集合了很多基础算法知识 值得补一下:
第二场组队赛H题 CodeChef - CRYPCUR
https://vjudge.net/contest/224062#problem/H
题意:
给若干条有向边 求一条可以经过所有点至少一次的路径的路径中边权的最小值
记得在SCAU的校赛做过一道类似的 当时是一个图 里面有很多有等级怪物 当你的等级不小于怪物的等级时你才不会死 问你从起点到终点最少需要多少的初始等级 同时问你具有了这个等级时最短要走多少步 比较好的做法是二分等级 假设当前的等级就是答案 然后把大于这个等级的怪物当做是墙 跑一个BFS就能过了 这道题也是一样的道理 我们也可以二分我们的最小边权 假设这个就是答案 看他能否经过所有的节点至少一次 但是问题就来了 我们怎么判断能否满足上述条件呢
比较容易想到的是用拓扑排序 如果有一个有向图满足入度为0的节点只有一个且可以拓扑排序的话那他就可以满足上述条件 可是拓扑排序是要在无环图中使用的 那么我们就可以用tarjan算法合并所有在一个连通块的节点 把他们看成一个新的节点 完成这个步骤后所得的自然就是一个有向无环图了 然后就可以用拓扑排序了 记得要先判是否入度为0的节点只有一个
AC代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <bitset> #include <vector> #include <string> #include <deque> #include <list> #include <set> #include <map> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; typedef long long ll; const int N=1e5+50; const ll MAXN=1e18; const int MAX=1e5+10; const ll mod=1e9+7; const int inf=0x3f3f3f3f; const ll INF=0x3f3f3f3f3f3f3f3f; const double eps=1e-7; int n,m; struct node { int to,next; }edge[N]; struct Edge { int u,v,w; }p[N]; int head[N],tot; int low[N],dfn[N],Stack[N],belong[N],du[N]; int Index,top; int scc; bool instack[N]; inline void addedge(int u,int v) { edge[tot].to=v; edge[tot].next=head[u]; head[u]=tot++; } inline void tarjan(int u) { int v; low[u]=dfn[u]=++Index; Stack[top++]=u; instack[u]=true; for(int i=head[u];i!=-1;i=edge[i].next) { v=edge[i].to; if(dfn[v]==0) { tarjan(v); if(low[u]>low[v]) low[u]=low[v]; } else if(instack[v]&&low[u]>dfn[v]) { low[u]=dfn[v]; } } if(low[u]==dfn[u]) { scc++; do { v=Stack[--top]; instack[v]=false; belong[v]=scc; } while(v!=u); } } inline void Solve(int n) { memset(dfn,0,sizeof(dfn)); memset(instack,0,sizeof(instack)); Index=scc=top=0; for(int i=1;i<=n;i++) { if(dfn[i]==0) tarjan(i); } } void init() { tot=0; memset(head,-1,sizeof(head)); memset(du,0,sizeof(du)); } bool vis[N]; inline bool check(int x) { init(); for(int i=0;i<m;i++) { if(p[i].w<=x) addedge(p[i].u,p[i].v); } Solve(n); init(); for(int i=0;i<m;i++) { if(p[i].w<=x) { if(belong[p[i].u]!=belong[p[i].v]) { du[belong[p[i].v]]++; addedge(belong[p[i].u],belong[p[i].v]); } } } int cnt=0; queue<int>q; memset(vis,0,sizeof(vis)); while(!q.empty()) q.pop(); for(int i=1;i<=scc;i++) { if(du[i]==0) q.push(i); } while(!q.empty()) { if(q.size()>1) return false; int u=q.front(); q.pop(); vis[u]=true; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; du[v]--; if(!du[v]) q.push(v); } } for(int i=1;i<=scc;i++) { if(vis[i]==0) return false; } return true; } inline void solve() { scanf("%d",&n); scanf("%d",&m); for(int i=0;i<m;i++) { scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w); } int l=1,r=1e9+10; if(!check(r)) { printf("Impossible\n"); return; } while(l+1<r) { int mid=(r+l)>>1; if(check(mid)) { r=mid; } else l=mid; } if(check(l)) { printf("%d\n",l); } else printf("%d\n",r); return ; } int main() { int casen=1; cin>>casen; while(casen--) { solve(); } return 0; }
第三场的C题ZOJ - 4006
https://vjudge.net/problem/ZOJ-4006
题意:一条坐标轴 你初始点为x=0 到x+1和x-1的概率为四分之一 不动的概率是二分之一 设在n次后到达m点的概率为P/Q 求P*(Q的乘法逆元)注意PQ是互质的
这道题用了一些数论知识
设向前走了x 后走y 不动z次 用概率知识可以推演出一条方程
也就是说我们要解决组合数取模 和除法取模的问题
方法详见
https://blog.csdn.net/acdreamers/article/details/8037918(组合数取模)
https://blog.csdn.net/zhoujl25/article/details/62419372(除法取模)
然后就可以做了
AC代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <bitset> #include <vector> #include <string> #include <deque> #include <list> #include <set> #include <map> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; typedef long long ll; const int maxn=1000010; const int mod=1000000007; int p[maxn]; int fac[maxn]; int inv[maxn]; int n,m; inline int C(int n,int m) { return n<m?0:1LL*fac[n]*inv[m]%mod*inv[n-m]%mod; } int main() { int i; for(fac[0]=fac[1]=1,i=2;i<maxn;i++) fac[i]=1LL*fac[i-1]*i%mod; for(inv[0]=inv[1]=1,i=2;i<maxn;i++) inv[i]=1LL*(mod-inv[mod%i])*(mod/i)%mod; for(int i=2;i<maxn;i++) inv[i]=1LL*inv[i-1]*inv[i]%mod; for(p[0]=1,p[1]=inv[2],i=2;i<maxn;i++) p[i]=1LL*p[i-1]*p[1]%mod; int t;cin>>t; while(t--) { scanf("%d%d",&n,&m); int ans=0; for(int i=0;i<=n;i++) { int x=i; int y=i-m; int z=n-x-y; if(y<0||y>n||z<0||z>n) continue; ans=(1LL*C(n,x)*C(n-x,y)%mod*p[2*x+2*y+z]+ans)%mod; } printf("%d\n",ans); } }
再贴一道题:
组队赛第三场E题 ZOJ - 4008
https://vjudge.net/problem/ZOJ-4008
题意 给一课树 m次询问 每次问一个区间 问位于这个区间的连通块的数目
显然 连通块的数目=位于这个区间的点-位于这个区间的边的数目
点是l-r+1 那么问题的关键就是求位于这个区间的点啦 再把问题转化一下 其实就是给你 n个数对 在m次询问中 每次给你一个数对 问你在n个数对中有多少个数对是两个值都小于等于他的 那就把问题转化为了一个经典的二维逆序对问题 二维逆序对比较好的做法就是第一维排序 第二位用树状数组:
AC代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <bitset> #include <vector> #include <string> #include <deque> #include <list> #include <set> #include <map> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; typedef long long ll; struct node { int num; int son[4]; } tree[2000000]; int total; int answer; void update(int temp,int left,int right,int up,int down,int x,int y) { if(left==right&&up==down) { tree[temp].num=1; return ; } int mid1=left+right>>1; int mid2=up+down>>1; if(x<=mid1) { if(y<=mid2) { if(tree[temp].son[0]==0) { tree[temp].son[0]=++total; } update(tree[temp].son[0],left,mid1,up,mid2,x,y); } else { if(tree[temp].son[2]==0) { tree[temp].son[2]=++total; } update(tree[temp].son[2],left,mid1,mid2+1,down,x,y); } } else { if(y<=mid2) { if(tree[temp].son[1]==0) { tree[temp].son[1]=++total; } update(tree[temp].son[1],mid1+1,right,up,mid2,x,y); } else { if(tree[temp].son[3]==0) { tree[temp].son[3]=++total; } update(tree[temp].son[3],mid1+1,right,mid2+1,down,x,y); } } tree[temp].num=0; for(int i=0; i<4; i++) { tree[temp].num+=tree[tree[temp].son[i]].num; } } void query(int temp,int left,int right,int up,int down,int x,int y) { if(left>=x&&down<=y) { answer+=tree[temp].num; return; } int mid1=left+right>>1; int mid2=up+down>>1; if(x<=mid1) { if(y>mid2) { if(tree[temp].son[2]) { query(tree[temp].son[2],left,mid1,mid2+1,down,x,y); } } if(tree[temp].son[0]) { query(tree[temp].son[0],left,mid1,up,mid2,x,y); } } if(y>mid2) { if(tree[temp].son[3]) { query(tree[temp].son[3],mid1+1,right,mid2+1,down,x,y); } } if(tree[temp].son[1]) { query(tree[temp].son[1],mid1+1,right,up,mid2,x,y); } } int main() { int T; cin>>T; while(T--) { total=1; memset(tree,0,sizeof(tree)); int n,m; cin>>n>>m; for(int i=1; i<n; i++) { int a,b; scanf("%d%d",&a,&b); if(a>b) swap(a,b); update(1,1,n,1,n,a,b); } for(int i=0; i<m; i++) { int a,b; scanf("%d%d",&a,&b); answer=0; query(1,1,n,1,n,a,b); printf("%d\n",b-a-answer+1); } } }
扩展一下 二维逆序对问题可以用sort+BIT做 可是如果是三维的话就要用到CDQ分治了 更高维的话就要用嵌套的CDQ分治了
题目量提升后发现 一道做不出的题其实好像往往可以把他转化 分解成几个以前做过的比较经典的问题来做