Wannafly挑战赛14 题解

A

在三维空间中,平面 x = 0, y = 0, z = 0,以及平面 x + y + z = K 围成了一个三棱锥。
整天与整数打交道的小明希望知道这个三棱锥内、上整点的数目。

他觉得数量可能很多,所以答案需要对给定的 M 取模。

思路1:

对于一个三菱锥来说,其是由k+1个直角三角形组成,仅仅考虑其整数点

对于其中一个直角三角形来说,其中的整数点为(i^2 + i) /2

于是三菱锥中的整数点为: 1/2*∑(i^2 + i) (1<=i<=k+1)

由平方和公式: 


于是∑i^2 = (k+1)*(k+2)*(2k+3) / 6 

∑i = (k+1)*(k+2) / 2

1/2*∑(i^2 + i) (1<=i<=k+1)  =  1/12 * (k+1)*(k+2)*(2k+6)

= (k+1) * (k+2) * (k+3) / 6


思路2:

题中的意思就是找到 满足 a + b + c <= n的元组个数:

而 a + b + c <= n 可以转换为 a + b + c + d = n的四元组个数;

利用隔板法,将 k 个 1 分成 4 份,

其中允许a,b,c,d为空,于是我们人为的给a,b,c,d都加上1,于是就满足了隔板法的定义

于是 k + 4个1中有 k + 3 个空位,插入三个板子将其分成4份

就是 C(k+3,3) = (k+1)*(k+2)*(k+3) / 6


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mod;
int main()
{
    ll k;
    int caset;scanf("%d",&caset);
    while(caset--)
    {
        scanf("%lld%lld",&k,&mod);
        printf("%lld\n",(k+3)*(k+2) % (6*mod) * (k+1) % (6*mod) / 6);
    }
    return 0;
}

C

题意:

给出一个 0 ≤ N ≤ 105 点数、0 ≤ M ≤ 105 边数的有向图,

输出一个尽可能小的点集,使得从这些点出发能够到达任意一点,如果有多个这样的集合,输出这些集合升序排序后字典序最小的。

题解:

这里我们分析两个解法:

解法1: Tarjan缩点

通过Tarjan算法进行强连通分量的染色.

对于两个强连通分量a,b来说 有三种情况

1.要么a,b之间没有边相连  ///那么这个子集必须包括 a 的 点 和  b 的点

2.要么 a 能到 b                       ///这个最小子集只需要有a的点即可

3.要么 b 能到 a                       ///这个最小的子集只需要有b的点即可

以上的操作可以通过入度数组来维护

注意从强联通分量选择点时,也应当选择最小的点,最后需要排序

复杂度: O(n+m)

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+10;    /// 点数
const int MAXM = 1e5+10;    /// 边数
typedef struct node{
    int to,next;
}Edge;
Edge edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; /// Belong数组的值是1~scc
int Index,top;  /// Low 和 DFN 的遍历顺序
int scc;        /// 强连通分量的个数
bool Instack[MAXN];
vector<int> id[MAXN];
void addedge(int u,int v) {
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void Tarjan(int u) {
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for(int i = head[u];~i;i = edge[i].next) {
        v = edge[i].to;
        if(!DFN[v]) {
            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;
            id[scc].push_back(v);
        }
        while( v != u);
    }
}
void solve(int N) {
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
    for(int i=0;i<MAXN;i++) id[i].clear();
    Index = scc = top = 0;
    for(int i=1;i<=N;i++) if(!DFN[i]){
        Tarjan(i);
    }
}
int u[MAXM],v[MAXM],in[MAXN];
vector<int> ans;
void init() {
    tot = 0;
    memset(head,-1,sizeof(head));

    memset(in,0,sizeof(in));
    ans.clear();
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1;i<=m;i++) {
            scanf("%d%d",&u[i],&v[i]);
            addedge(u[i],v[i]);
        }
        solve(n);
        for(int i=1;i<=m;i++) {
            if(Belong[u[i]] == Belong[v[i]]) continue;
            in[Belong[v[i]]]++;
        }
        for(int i=1;i<=scc;i++) sort(id[i].begin(),id[i].end());
        for(int i=1;i<=scc;i++) if(!in[i]) ans.push_back(id[i][0]);
        sort(ans.begin(),ans.end());
        printf("%d\n",(int)ans.size());
        for(int i=0;i<(int)ans.size();i++)
            printf("%d%c",ans[i]," \n"[i+1 == (int)ans.size()]);
    }
    return 0;
}

解法2:

比赛结束后,无意中看到的一种思路

首先我们可以标记所有的点的入度

对于入度为0的点u进行深搜,把所有能到达u的点的父节点标记为u,个人感觉变成了1个以根节点u的树

遍历完后,图中还有一些没有遍历到的节点,这些节点是自成一个环的节点集

对于这些节点进行深搜,因为搜索顺序是从小到大,所以,最小的节点作为了父节点

最后,所有的父节点fnt[i] == i的节点就是我们要的子集

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
vector<int> way[maxn],ans;
int in[maxn];
int fnt[maxn];
void init() {
    ans.clear();
    memset(in,0,sizeof(in));
    memset(fnt,0,sizeof(fnt));
    for(int i=0;i<maxn;i++) way[i].clear();
}
void dfs(int x,int fa) {

    if(fnt[x] == fa) return;
    fnt[x] = fa;
    for(int i=0;i<way[x].size();i++) {
        dfs(way[x][i],fa);
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        while(m--) {
            int u,v;
            scanf("%d%d",&u,&v);
            way[u].push_back(v);
            in[v]++;
        }
        for(int i=1;i<=n;i++) if(in[i] == 0) {
            dfs(i,i);
        }
        for(int i=1;i<=n;i++) if(fnt[i] == 0) {
            dfs(i,i);
        }
        for(int i=1;i<=n;i++) if(fnt[i] == i) {
            ans.push_back(i);
        }
        int len = ans.size();
        printf("%d\n",len);
        for(int i=0;i<len-1;i++) printf("%d ",ans[i]);
        printf("%d\n",ans[len-1]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值