有一些题集合了很多基础算法知识 值得补一下:
第二场组队赛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.youkuaiyun.com/acdreamers/article/details/8037918(组合数取模)
https://blog.youkuaiyun.com/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分治了
题目量提升后发现 一道做不出的题其实好像往往可以把他转化 分解成几个以前做过的比较经典的问题来做