校内赛 2017.8.27 【卢卡斯定理】 【DFS+Hash】【线段树】

Description

crf 是一个天才。
他出生的第一秒,就已经学完了整本《组合数学》。
他觉得作为高中生的你,应该比才出生一秒的他差不到哪去,于是他决定向你问一个问题。
crf 想知道,从n 个物品中选出m 个的方案数,由于他很讨厌很长的答案,所以他想知道模p
后的答案。

Input

输入的第一行为三个整数n; m; p。

Output

输出共一行,为一个数字,代表答案。

Sample

Sample Input Sample Output
5 3 7 3
Sample Input Sample Output
125 77 71 63

Hint

对于30% 的数据,满足1 m n 10; 1 p 10。
对于70% 的数据,满足1 m n 100000。
对于100% 的数据,满足1 m n 1000000000; 1 p 100000。
对于100% 的数据,满足p 为质数。

思路

卢卡斯定理即可

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int N=1e9;
long long n,m,mod;
long long qmul(long long a,long long b)
{
    long long rt;
    for (rt=0;b;b>>=1,a=(a+a)%mod)
    if (b&1) {rt+=a;rt%=mod;}
    return rt;
}
long long qpow(long long a,long long b)
{
    long long rt;
    for (rt=1;b;b>>=1,a=qmul(a,a)%mod)
    if (b&1) rt=qmul(rt,a)%mod;
    return rt;
}
long long C(long long n,long long m)
{
    long long ans=1;
    for (long long i=1;i<=m;i++)
    {
        long long a=(n+i-m)%mod;
        int b=i%mod;
        ans=ans*(a*qpow(b,mod-2)%mod)%mod;
    }
    return ans;
}
long long lucas(long long n,long long m)
{
    if (m==n) return 1;
    return (C(n%mod,m%mod)*lucas(n/mod,m/mod))%mod;
}
int main()
{
    freopen("first.in","r",stdin);
    freopen("first.out","w",stdout);
    scanf("%I64d%I64d%I64d",&n,&m,&mod);
    if (m>n) {printf("0");return 0;}
    printf("%I64d",lucas(n,m)%mod);
    return 0;
}

Description

crf 是一个天才。
他出生的第二秒,往窗外望了一眼,看到了窗外的树林。
他发现这片树林非常有趣,因为它只有一排树,从左到右依次排列。
天才的crf 马上就把真实的树的结构抽象成为了图论中的树(即任意两个顶点有且仅有一条路
径可以互相到达的图)。
crf 不仅头脑天才,而且他的身体素质也堪称天才,这也包括了他可以在0.01 秒内用AWP 爆
掉队友的头的动态视力。在这一秒内他发现非常多的树其实稍微变一下方向就是长得一样的。
他想知道,在这片树林中有多少棵树和他现在脑中yy 的这棵树是本质相同的。
两棵树本质相同的定义是,对于树A,存在一种顶点编号的排列,使得树A 的顶点编号重排
后,树A 的每一条边的方向和两个顶点都和树B 对应相同。
注意,虽然图论中大多数对树的定义都是指无根树,即每条树边都是无向边的树,但在这个问
题中的树是现实中的树的抽象,所以我们认为这些树都是有根树。
同时为了方便起见,我们假设每棵树具有相同的结点数。

Input

第一行为三个整数n; m; q,表示这排树林树的数量、每棵树的结点数和询问的数量。
接下来n 部分,每部分有m��1 行,表示一棵树的m��1 条边,每行有两个整数x; y,表示从
x 到y 有一条边。
接下来q 部分,每部分有m��1 行,表示询问中的一棵树的m��1 条边,每行有两个整数x; y,
表示从x 到y 有一条边。

Output

输出有q 行,每行一个整数,表示该次询问有多少棵树和crf 想象的那棵树本质相同。

思路

经典的树同构,构造括号序列,在每个结点处把子树的括号序列进行排序并且加上一对括号即可。
一般的做法是将左括号和右括号分别赋一个质数的值,用hash值进行比较和记录,怕卡hash的话可以使用双hash来保险,本题因为复杂度的原因标程使用了直接用括号序列进行比较。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=10000+5;
int n,m,q,num,ans=0,tot=0,cnt;
int head[N],get[N][4],headd[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge
{
    int v,next;
};
edge ed[N],now[N];
struct data
{
    int in,out;
    int flag;
};
data tree[N],tr[N];
void build(int u,int v)
{
    ed[++num].v=v;
    ed[num].next=head[u];
    head[u]=num;
    tree[u].out++;
    tree[v].in++;
}
void add(int u,int v)
{
    now[++cnt].v=v;
    now[cnt].next=headd[u];
    headd[u]=cnt;
    tr[u].out++;
    tr[v].in++;
}
int main()
{
    freopen("second.in","r",stdin);
    freopen("second.out","w",stdout);
    n=read();m=read();q=read();
    if (m==1) {printf("%d\n",n);return 0;}
    memset(head,-1,sizeof(head));
    memset(tree,0,sizeof(tree));
    memset(tr,0,sizeof(tr));
    for (int k=1;k<=n;k++)
    {
        int now=tot*(m-1);
        for (int i=1;i<m;i++)
        {
            int x,y;
            x=read();y=read();
            get[i+now][1]=x;get[i+now][2]=y;
        }
        tot++;
    }//100000
    tot=0;
    while(q--)
    {
        memset(tr,0,sizeof(tr));
        memset(headd,-1,sizeof(headd));
        for (int i=1;i<m;i++)
        {
            int x,y;
            x=read();y=read();
            add(x,y);
        }
        for (int k=1;k<=n;k++)
        {
            memset(head,-1,sizeof(head));
            memset(tree,0,sizeof(tree));
            for (int i=1;i<=n;i++)
            tr[i].flag=0;
            int u,v;
            for (int i=1;i<m;i++)
            {
                u=get[i+tot*(m-1)][1],v=get[i+tot*(m-1)][2];
                build(u,v);
            }
            for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
            if(tr[i].in==tree[j].in&&tr[i].out==tree[j].out&&tree[j].flag==0)
            {tree[j].flag=1;tr[i].flag=1;}
            for (int i=1;i<=n;i++)
            if (tree[i].flag!=1) {ans--;break;}
            ans++;tot++;
        }
        printf("%d\n",ans);
        ans=0;tot=0;
    }
    return 0;
}

Description

crf 是一个天才。
他出生的第三秒,偶然看到了一张简谱,可能是医院的工作人员闲的没事的时候创作的。
当时的他并不能理解简谱和音乐的关系(但这并不妨碍他今后成为世界著名小提琴演奏家),
他只是单纯的看着这张写满1-7 的纸,觉得非常缺乏美感。他认为有序的数列才是最美的。
但毕竟他才出生第三秒,他的mogic 也只是初步显现,他可以改变一段连续的音符的顺序,但
他的mogic 不足以一次改掉整张简谱。
这是crf 这辈子第一次犯蠢,也是唯一一次犯蠢。他尝试着将一段一段的音符调成升序,但他
并没有意识到就算这样调了也不能让整个序列变得有序。
crf 的mogic 可以选出一段连续的音符,将它们重新排为升序之后再放回,他尝试了一些这样
的操作之后发现并没有将序列变得有序就放弃了。
后来的程序员为了纪念伟大的crf,将他唯一犯蠢的这段经历重新变成了一道算法题。
虽然这道题大部分题面都是对crf 的赞美,但可能你们不敢兴趣所以就直接上题吧。
给定一个长度为n 的只有1-7 的数字的序列,有q 次操作,每次操作是将一段连续的数字拿出
来,排成升序或者降序后放回。求最终序列。

Input

输入的第一行为两个整数n; q,表示序列的长度和操作的数量。
接下来一行有n 个整数,表示crf 的数字序列。
接下来q 行,每行有三个整数l; r; op,l; r 含义如题目中所示,op 为0 表示升序,为1 表示降
序。

Output

输出共1 行,包含n 个整数,表示最终的序列,中间用空格隔开。
Sample Input Sample Output
5 2
3 1 2 5 4
1 3 0
2 5 1
1 5 4 3 2

Hint

对于30% 的数据,保证1 n; q 1000。

思路

线段树,因为只有7种数字,用线段树维护每一段的7种数字分别有多少,每次操作即为一次查询和七次覆盖

代码

#include<cstdio>
#include<iostream>
#include<cstring>
const int maxn = 100010;

struct Node
{
    int cnt[7];
    int size;
    int cover;

    Node()
    {
        memset(cnt, 0, sizeof(cnt));
        cover = -1;
    }

    inline void set(int cover)
    {
        this->cover = cover;
        for (int i = 0; i < 7; i ++)
            if (i == cover)
                cnt[i] = size;
            else
                cnt[i] = 0;
    }

    inline friend Node operator + (const Node &a, const Node &b)
    {
        Node c;
        for (int i = 0; i < 7; i ++)
            c.cnt[i] = a.cnt[i] + b.cnt[i];
        return c;
    }
}t[maxn << 2];

int a[maxn];

inline void update(int rt)
{
    t[rt].size = t[rt << 1].size + t[rt << 1 | 1].size;
    for (int i = 0; i < 7; i ++)
        t[rt].cnt[i] = t[rt << 1].cnt[i] + t[rt << 1 | 1].cnt[i];
}

inline void pushdown(int rt)
{
    if (t[rt].cover != -1)
    {
        t[rt << 1].set(t[rt].cover);
        t[rt << 1 | 1].set(t[rt].cover);
        t[rt].cover = -1;
    }
}

inline void build(int l, int r, int rt)
{
    if (l == r)
    {
        t[rt].size = 1;
        t[rt].cnt[a[l]] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    update(rt);
}

inline Node query(int l, int r, int rt, int L, int R)
{
    if (l >= L && r <= R)
        return t[rt];
    pushdown(rt);
    int mid = (l + r) >> 1;
    Node answer;
    if (L <= mid)
        answer = answer + query(l, mid, rt << 1, L, R);
    if (R > mid)
        answer = answer + query(mid + 1, r, rt << 1 | 1, L, R);
    return answer;
}

inline void change(int l, int r, int rt, int L, int R, int cover)
{
    if (l >= L && r <= R)
    {
        t[rt].set(cover);
        return;
    }
    pushdown(rt);
    int mid = (l + r) >> 1;
    if (L <= mid)
        change(l, mid, rt << 1, L, R, cover);
    if (R > mid)
        change(mid + 1, r, rt << 1 | 1, L, R, cover);
    update(rt);
}

int main()
{    freopen("third.in","r",stdin);
    freopen("third.out","w",stdout);
    int n, q;
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i ++)
    {
        scanf("%d", &a[i]);
        a[i] --;
    }
    build(1, n, 1);
    for (int i = 1; i <= q; i ++)
    {
        int l, r, op;
        scanf("%d%d%d", &l, &r, &op);
        Node answer = query(1, n, 1, l, r);
        int nowl = l;
        if (op == 0)
        {
            for (int j = 0; j < 7; j ++)
                if (answer.cnt[j])
                {
                    change(1, n, 1, nowl, nowl + answer.cnt[j] - 1, j);
                    nowl += answer.cnt[j];
                }
        }
        else
        {
            for (int j = 6; j >= 0; j --)
                if (answer.cnt[j])
                {
                    change(1, n, 1, nowl, nowl + answer.cnt[j] - 1, j);
                    nowl += answer.cnt[j];
                }
        }
    }
    for (int i = 1; i <= n; i ++)
    {
        Node answer = query(1, n, 1, i, i);
        int x = -1;
        for (int j = 0; j < 7; j ++)
            if (answer.cnt[j])
                x = j + 1;
        printf("%d%c", x, i == n ? '\n' : ' ');
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值