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;
}