组队赛总结(二)

有一些题集合了很多基础算法知识 值得补一下:

第二场组队赛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分治了

题目量提升后发现 一道做不出的题其实好像往往可以把他转化 分解成几个以前做过的比较经典的问题来做

猜你喜欢

转载自blog.csdn.net/weixin_39302444/article/details/80080976
今日推荐