A.牛妹的游戏
题意:
n
个
点
的
完
全
图
,
m
条
边
属
于
一
类
阵
营
n个点的完全图,m条边属于一类阵营
n个点的完全图,m条边属于一类阵营
另
外
m
条
边
属
于
另
一
类
阵
营
另外m条边属于另一类阵营
另外m条边属于另一类阵营
给
出
你
n
,
m
和
这
m
条
边
,
问
相
同
阵
营
是
否
存
在
一
个
三
元
环
给出你n,m和这m条边,问相同阵营是否存在一个三元环
给出你n,m和这m条边,问相同阵营是否存在一个三元环
题解:
通
过
画
图
观
察
可
以
发
现
通过画图观察可以发现
通过画图观察可以发现
n
<
=
5
的
时
候
n<=5的时候
n<=5的时候
将
每
个
点
依
次
相
连
,
首
尾
相
接
,
就
不
会
存
在
同
阵
营
三
元
环
将每个点依次相连,首尾相接,就不会存在同阵营三元环
将每个点依次相连,首尾相接,就不会存在同阵营三元环
但
是
当
n
>
5
的
时
候
但是当n>5的时候
但是当n>5的时候
如
果
再
次
这
样
,
你
就
会
发
现
,
可
以
在
内
部
成
为
一
个
三
元
环
如果再次这样,你就会发现,可以在内部成为一个三元环
如果再次这样,你就会发现,可以在内部成为一个三元环
但
是
如
果
加
上
三
元
环
的
其
中
一
个
边
,
就
会
构
造
成
另
外
一
个
阵
营
的
三
元
环
但是如果加上三元环的其中一个边,就会构造成另外一个阵营的三元环
但是如果加上三元环的其中一个边,就会构造成另外一个阵营的三元环
看
题
解
这
道
题
是
个
拉
姆
塞
结
论
,
大
概
就
是
n
大
于
5
必
有
三
元
环
这
种
意
思
看题解这道题是个拉姆塞结论,大概就是n大于5必有三元环这种意思
看题解这道题是个拉姆塞结论,大概就是n大于5必有三元环这种意思
n
<
5
的
时
候
,
就
可
以
用
邻
接
矩
阵
存
图
n<5的时候,就可以用邻接矩阵存图
n<5的时候,就可以用邻接矩阵存图
直
接
枚
举
任
意
三
个
点
,
看
这
三
个
点
之
间
的
边
同
属
于
这
m
条
边
或
者
都
不
属
于
即
可
直接枚举任意三个点,看这三个点之间的边同属于这m条边或者都不属于即可
直接枚举任意三个点,看这三个点之间的边同属于这m条边或者都不属于即可
AC代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int g[10][10];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
if(n>5){
cout<<"yes"<<endl;
for(int i=0;i<m;i++){int u,v;cin>>u>>v;}
continue;
}
memset(g,0,sizeof g);
for(int i=0;i<m;i++){
int u,v;
cin>>u>>v;
g[u][v]=g[v][u]=1;
}
bool f=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++){
if(i==j||j==k||i==k)continue;
if(g[i][j]==g[i][k]&&g[i][k]==g[j][k]&&g[j][k]==g[i][j])f=1;
}
if(f)cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
return 0;
}
B.病毒扩散
题意:
(
0
,
0
)
处
有
一
个
感
染
者
(0,0)处有一个感染者
(0,0)处有一个感染者
在
每
1
s
每
个
点
都
会
往
右
边
的
一
个
点
和
上
边
的
一
个
点
扩
散
一
个
感
染
者
在每1s每个点都会往右边的一个点和上边的一个点扩散一个感染者
在每1s每个点都会往右边的一个点和上边的一个点扩散一个感染者
q
次
询
问
在
(
x
,
y
)
在
t
s
的
时
候
有
多
少
感
染
者
q次询问在(x,y)在t~s的时候有多少感染者
q次询问在(x,y)在t s的时候有多少感染者
题解:
问
题
模
型
转
换
问题模型转换
问题模型转换
可
以
转
换
成
某
个
人
t
s
的
时
间
到
达
(
x
,
y
)
的
方
案
数
可以转换成某个人t~s的时间到达(x,y)的方案数
可以转换成某个人t s的时间到达(x,y)的方案数
每
秒
它
可
以
不
动
,
右
移
或
者
上
移
每秒它可以不动,右移或者上移
每秒它可以不动,右移或者上移
右
移
和
上
移
就
代
表
正
常
的
每
秒
的
扩
散
右移和上移就代表正常的每秒的扩散
右移和上移就代表正常的每秒的扩散
不
动
就
代
表
在
某
个
点
的
下
一
秒
或
者
以
后
几
秒
对
(
x
,
y
)
的
扩
散
数
不动就代表在某个点的下一秒或者以后几秒对(x,y)的扩散数
不动就代表在某个点的下一秒或者以后几秒对(x,y)的扩散数
问
题
模
型
转
换
好
了
,
就
很
好
写
了
问题模型转换好了,就很好写了
问题模型转换好了,就很好写了
使
用
组
合
数
,
C
(
t
,
x
)
∗
C
(
t
−
x
,
y
)
使用组合数,C(t,x)*C(t-x,y)
使用组合数,C(t,x)∗C(t−x,y)
从
t
s
里
选
出
x
s
和
y
s
右
移
和
上
移
即
可
从ts里选出xs和ys右移和上移即可
从ts里选出xs和ys右移和上移即可
AC代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
//const int mod=1e9+7;
const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
ll fact[maxn],inv1[maxn];
ll Pow(ll a, ll b){
ll ans = 1;
while(b > 0){
if(b & 1){
ans = ans * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return ans;
}
//逆元
ll inv(ll b){
return Pow(b,mod-2)%mod;
}
ll C(ll n,ll m){
if(m==0||m==n) return 1;
ll res=(fact[n]*inv1[m]%mod*inv1[n-m])%mod;
return res;
}
void init() {
fact[0] = 1;
for (int i = 1; i < maxn; i++) {
fact[i] = fact[i - 1] * i %mod;
}
inv1[maxn - 1] = Pow(fact[maxn - 1], mod - 2);
for (int i = maxn - 2; i >= 0; i--) {
inv1[i] = inv1[i + 1] * (i + 1) %mod;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int t;
cin>>t;
init();
while(t--){
ll x,y,t;
cin>>x>>y>>t;
if(x+y>t)cout<<0<<endl;
else cout<<(C(t,x)*C(t-x,y))%mod<<endl;
}
return 0;
}
C.牛牛染颜色
题意:
给
一
个
有
n
个
结
点
的
树
,
1
是
根
结
点
给一个有n个结点的树,1是根结点
给一个有n个结点的树,1是根结点
每
个
结
点
可
以
染
色
成
黑
色
或
者
白
色
每个结点可以染色成黑色或者白色
每个结点可以染色成黑色或者白色
如
果
两
个
结
点
是
黑
色
,
那
么
他
们
两
个
结
点
的
最
近
公
共
祖
先
也
必
须
是
黑
色
如果两个结点是黑色,那么他们两个结点的最近公共祖先也必须是黑色
如果两个结点是黑色,那么他们两个结点的最近公共祖先也必须是黑色
问
有
多
少
种
涂
色
方
案
问有多少种涂色方案
问有多少种涂色方案
题解:
树
形
D
P
树形DP
树形DP
用
d
p
[
u
]
[
0
/
1
]
表
示
树
上
结
点
u
染
黑
色
(
1
)
或
者
白
色
(
0
)
的
方
案
数
用dp[u][0/1]表示树上结点u染黑色(1)或者白色(0)的方案数
用dp[u][0/1]表示树上结点u染黑色(1)或者白色(0)的方案数
如
果
这
个
结
点
是
黑
色
,
那
么
他
的
任
何
一
个
子
树
都
可
以
是
黑
色
或
者
白
色
如果这个结点是黑色,那么他的任何一个子树都可以是黑色或者白色
如果这个结点是黑色,那么他的任何一个子树都可以是黑色或者白色
即
d
p
[
v
]
[
1
]
+
d
p
[
v
]
[
0
]
即dp[v][1]+dp[v][0]
即dp[v][1]+dp[v][0]
如
果
这
个
结
点
是
白
色
,
那
么
他
的
子
树
如果这个结点是白色,那么他的子树
如果这个结点是白色,那么他的子树
只
能
全
部
白
色
,
或
者
只
有
一
个
黑
色
的
只能全部白色,或者只有一个黑色的
只能全部白色,或者只有一个黑色的
由
于
要
考
虑
空
集
防
止
重
复
计
算
的
情
况
,
所
以
要
对
每
次
情
况
进
行
减
一
,
最
后
结
束
了
加
上
这
个
空
集
由于要考虑空集防止重复计算的情况,所以要对每次情况进行减一,最后结束了加上这个空集
由于要考虑空集防止重复计算的情况,所以要对每次情况进行减一,最后结束了加上这个空集
所
以
可
以
得
到
转
移
方
程
所以可以得到转移方程
所以可以得到转移方程
d p [ u ] [ 0 ] = ( d p [ u ] [ 0 ] + d p [ v ] [ 1 ] + d p [ v ] [ 0 ] − 1 ) dp[u][0]=(dp[u][0]+dp[v][1]+dp[v][0]-1) dp[u][0]=(dp[u][0]+dp[v][1]+dp[v][0]−1)
d p [ u ] [ 1 ] = d p [ u ] [ 1 ] ∗ ( d p [ v ] [ 1 ] + d p [ v ] [ 0 ] ) dp[u][1]=dp[u][1]*(dp[v][1]+dp[v][0]) dp[u][1]=dp[u][1]∗(dp[v][1]+dp[v][0])
最 后 的 答 案 即 d p [ 1 ] [ 1 ] + d p [ 1 ] [ 0 ] 最后的答案即dp[1][1]+dp[1][0] 最后的答案即dp[1][1]+dp[1][0]
AC代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
struct edge{
int v,nx;
}e[maxn<<1];
int head[maxn];
int cnt;
void add(int u,int v){
e[++cnt].v=v;
e[cnt].nx=head[u];
head[u]=cnt;
}
ll dp[maxn][2];
void dfs(int u,int fa){
dp[u][0]=dp[u][1]=1;
for(int i=head[u];i;i=e[i].nx){
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
dp[u][0]=(dp[u][0]+dp[v][1]+dp[v][0]-1)%mod;
dp[u][1]=dp[u][1]*(dp[v][1]+dp[v][0])%mod;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n;
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
dfs(1,0);
cout<<(dp[1][0]+dp[1][1])%mod<<endl;
return 0;
}
D. 牛牛的呱数
题意:
给
你
n
个
数
,
每
个
数
的
长
度
<
=
1
e
6
给你n个数,每个数的长度<=1e6
给你n个数,每个数的长度<=1e6
每
个
数
有
无
数
个
,
任
意
组
合
,
组
合
成
p
的
倍
数
每个数有无数个,任意组合,组合成p的倍数
每个数有无数个,任意组合,组合成p的倍数
问
这
样
组
成
数
最
小
的
长
度
问这样组成数最小的长度
问这样组成数最小的长度
题解:
由
于
n
<
=
100
,
p
<
=
200
由于n<=100,p<=200
由于n<=100,p<=200
所
以
可
以
考
虑
B
F
S
所以可以考虑BFS
所以可以考虑BFS
枚
举
每
种
情
况
,
维
护
一
下
再
向
p
取
余
的
结
果
x
和
长
度
y
此
时
的
最
短
长
度
枚举每种情况,维护一下再向p取余的结果x和长度y此时的最短长度
枚举每种情况,维护一下再向p取余的结果x和长度y此时的最短长度
每
次
连
接
这
n
个
数
,
然
后
加
入
队
列
每次连接这n个数,然后加入队列
每次连接这n个数,然后加入队列
每
次
新
的
d
x
=
(
x
+
1
0
y
∗
a
[
i
]
)
%
p
每次新的dx=(x+10^y*a[i])\%p
每次新的dx=(x+10y∗a[i])%p
然
后
B
F
S
每
次
当
x
=
0
的
时
候
取
这
个
结
果
,
最
终
取
最
小
值
即
可
然后BFS每次当x=0的时候取这个结果,最终取最小值即可
然后BFS每次当x=0的时候取这个结果,最终取最小值即可
AC代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod=1e9+7;
//const int mod=998244353;
const double eps = 1e-10;
const double pi=acos(-1.0);
const int maxn=1e6+10;
const ll inf=0x3f3f3f3f;
const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int a[110];
int dis[210][210];
int len[110];
int b[maxn];
struct node{
int a,b,len;
};
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,p;
cin>>n>>p;
b[0]=1;
for(int i=1;i<maxn;i++)b[i]=b[i-1]*10%p;
for(int i=1;i<=n;i++){
string s;
cin>>s;
len[i]=s.length();
for(int j=0;j<len[i];j++)
a[i]=(a[i]*10+s[j]-'0')%p;
}
int ans=inf;
memset(dis,inf,sizeof dis);
queue<node> q;
for(int i=1;i<=n;i++){
int da=a[i];
int db=b[len[i]];
int dlen=len[i];
if(dis[da][db]>dlen){
dis[da][db]=dlen;
q.push((node){da,db,dlen});
}
}
while(!q.empty()){
node x=q.front();
q.pop();
if(x.len>dis[x.a][x.b])continue;
if(!x.a)ans=min(ans,x.len);
for(int i=1;i<=n;i++){
int da=(x.a+a[i]*x.b)%p;
int db=x.b*b[len[i]]%p;
int dlen=x.len+len[i];
if(dlen<dis[da][db]){
dis[da][db]=dlen;
q.push((node){da,db,dlen});
}
}
}
if(ans>=inf)cout<<-1;
else cout<<ans;
return 0;
}