原题链接
A题(签到)
K题(辗转相减数列)
题目
算法思路
- 类似辗转相减法,找规律
- 可以证明,s1,s2交换后,答案不变(虽然我们证明不了)(训练的时候,杰神打了一个暴力,来验证)
- 所以这样就可以只考虑a>b的情况,例如13,3得到的就是13,3,10,7,3,4,3,1,转换一下,可以看为13,10,7,4,1显然可以发现里面是每次减少3,3到13,辗转相除得到(3,1),其中出现了13/3个新的数(每次都不看第一个数a)
- 最后在特判一下边界条件有0的即可
代码实现
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define el '\n'
#define cl putchar('\n')
#define pb push_back
#define eb emplace_back
#define fir first
#define sec second
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vci;
typedef map<int,int> mii;
typedef mii::iterator mii_it;
const int N=1e5+10,M=1e3+10;
int T,n,m;
ll x,y,ans;
//这一段暴力用于证明上述结论
set<ll> st;
void gcd(ll a,ll b){
if(b==0)return;
st.insert(abs(a-b));
gcd(b,abs(a-b));
}
void gcd1(ll a,ll b){
if(b==0||a==0)return;
if(b>a)swap(a,b);//可以证明交换不影响结果
ans+=a/b;
gcd1(b,a%b);
}
int main() {
cin.tie(0);
cout.tie(0);
cin>>T;
for(int p=1;p<=T;p++) {
cin>>x>>y;
cout<<"Case #"<<p<<": ";
if(x==0&&y==0)cout<<1<<endl;
else if(x==0||y==0)cout<<2<<endl;
else {
ans=1;
gcd1(x,y);
cout<<ans<<endl;
}
}
}
F题(花朵染色)
题目大意
给定n个花朵,共有m种颜料,求只用k种颜色,且相同颜色不能相邻的方案数,结果取模
算法思路
- 容斥原理
- 用2-k种颜色的方案数,第一个位置有k种颜色的填法,后续的每个位置,为了不跟前一个位置相同,都有(k-1)种颜色的填法,但是其中有2-(k-1)颜色的方案,所以要容斥原理扣除掉
- 容斥原理得: a n s = C k k × k ( k − 1 ) n − 1 − C k k − 1 ( k − 1 ) × ( k − 2 ) n − 1 + C k k − 2 × ( k − 2 ) ( k − 3 ) n − 1 + … … ans=C_k^{k}\times k(k-1)^{n-1}-C_k^{k-1} (k-1)\times (k-2)^{n-1}+C_k^{k-2}\times (k-2) (k-3)^{n-1}+…… ans=Ckk×k(k−1)n−1−Ckk−1(k−1)×(k−2)n−1+Ckk−2×(k−2)(k−3)n−1+……
- i从k到2,得到第i项为 ( − 1 ) k − i C k i ∗ i ∗ ( i − 1 ) n − 1 (-1)^{k-i}C_k^{i}*i*(i-1)^{n-1} (−1)k−iCki∗i∗(i−1)n−1
- 代码为
for(int i=k;i>=2;i--){
ans=(ans+op*C(k,i)*i%p*ksm(i-1,n-1)%p)%p;
op=-op;
}
- 训练的时候一直叫杰神放弃,因为分析了一波感觉A不出来,思路被带偏了,但是没有那么难,其实就一个容斥原理,所以有时候还是要看过的人数,啃一会儿实在A不出来再换题
代码实现
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define el '\n'
#define cl putchar('\n')
#define pb push_back
#define eb emplace_back
#define fir first
#define sec second
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vci;
typedef map<int,int> mii;
typedef mii::iterator mii_it;
const int N=1e6+100,M=1e3+10;
const long long p=1e9+7;
long long f[N],n,m,k;
long long ksm(long long a,long long b){//快速幂
long long ans=1;
while(b){
if(b&1)ans=ans*a%p;
a=a*a%p;
b=b/2;
}
return ans;
}
long long C1(long long n,long long m){//求解组合数C(m,k),for循环
long long ans=1;
for(long long i=n-m+1;i<=n;i++)ans=ans*i%p;
ans=ans*ksm(f[m],p-2)%p;//逆元
return ans;
}
long long C2(long long n,long long m){//求解组合数C(k,i),加上逆元,费马小定理可以log n 求出
return f[n]*ksm(f[m],p-2)%p*ksm(f[n-m],p-2)%p;
}
void init(){//初始化阶乘
f[0]=1;
for(int i=1;i<=N-10;i++)f[i]=f[i-1]*i%p;
}
int main() {
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
init();
for(int now=1;now<=T;now++){
cin>>n>>m>>k;
if(n==1){
printf("Case #%d: %lld\n",now,m);
continue;
}
long long op=1,ans=0;
for(int i=k;i>=2;i--){
ans=(ans+op*C2(k,i)*i%p*ksm(i-1,n-1)%p)%p;
op=-op;
}
ans=(ans%p+p)%p;//ans可能为负数
ans=ans*C1(m,k)%p;//最后答案乘以C(m,k)
printf("Case #%d: %lld\n",now,ans);
}
}
I 题(IP子网)
题目大意
给定n行,表示ip子网,求要覆盖所有ip的方案,l代表该ip的前i为覆盖了当前ip的前缀的所有子网
算法思路
- 给区间设立三种颜色,如果当前区间为红色,则表示当前子网已经全部被覆盖了直接返回
- 当前区间为蓝色,表示当前区间有子网,不能直接标红,需要继续递归儿子节点
- 区间为白色的时候,表示当前区间没有子网,可以直接标红
- 我们最终的目标就是要将所有的子网全部标红,即以(x=0,l=0)为根节点的树,的根节点标红即可
- 其次是细节问题,其实可以开long long,但是32位完全可以用unsigned int,因为int左移的话,符号位不会变,l=1位为1,就始终是负数,所以采用unsigned int
- 读入的话,借助scanf来读入,斜杠不用转移……反斜杠才需要转移,所以此处输出不用转移
- 输出的话同理,将u int转换为IP地址即可,所以此处输入输出要用预处理数组e[],e2[],方便直接取出任意8位
- n==0的时候要特判一下,还有初值问题,记得将f1,f2,ans给clear掉
- 训练的时候,题目都读了好久,快20分钟甚至半小时,英语还是很重要……大模拟题我和驰神一起看,驰神跟我思路,最后我们也是一发就AC了,想思路和细节想了一小时,代码十几分钟就完事了,特别是大模拟题更要想清楚再打
代码实现
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define el '\n'
#define cl putchar('\n')
#define pb push_back
#define eb emplace_back
#define fir first
#define sec second
#define int unsigned int
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vci;
typedef map<int,int> mii;
typedef mii::iterator mii_it;
const int N=1e5+10,M=1e3+10;
int T,n;
int l;
int a[4],e[35],e2[35];
set<pii> f1,f2,ans;
//f1代表当前区间是否标红,f2代表当前区间是否标蓝,ans用来储存答案(白色区间标红)
//区间为白色的时候,可以直接标红
//区间为蓝色的时候,因为会跟子网有交点所以不能直接标红,需要继续递归儿子节点
//区间为红色直接返回
void work(int x,int l){
f1.insert(make_pair(x,l));//对于l,标红
for(int i=1;i<l;i++){
f2.insert(make_pair(x&e[i],i));//(1~l-1)标蓝
}
return ;
}
void solve(int x,int l){
pii p0=make_pair(x,l);
if(f1.find(p0)!=f1.end())return ;//他是红色
if(f2.find(p0)==f2.end()){//他是白色,递归边界l=32必然是白色,这个时候直接返回就行
f1.insert(p0);//白色直接标红
ans.insert(p0);//放入答案
return ;
}
int now=x&e[l];//
int son2=x|(1<<(31-l));
pii p1=make_pair(x,l+1),p2=make_pair(son2,l+1);//p1代表左儿子,p2代表右儿子
if(f1.find(p1)==f1.end()){//左儿子不是红色
solve(x,l+1);//将左儿子标红
}
if(f1.find(p2)==f1.end()){//右儿子不是蓝色
solve(son2,l+1);
}
//经过上面四行,整个区间一定是红色,这样我们就可以把p0标红了
f1.insert(p0);
}
void Pr(pii p){
for(int i=1;i<=4;i++){//u int转ip地址
printf("%u",(p.first&e2[i])>>(8*(4-i)));//取出8个1之后还要右移
if(i<4)cout<<'.';
}
printf("/%u\n",p.sec);
}
signed main() {
cin.tie(0);
cout.tie(0);
int x;
for(int i=1;i<=32;i++){//初始化,前i位全部都是1
e[i]=(e[i-1]>>1)|(1<<31);
}
e2[1]=e[8];//前八位,连续8个1
e2[2]=e[16]^e[8];
e2[3]=e[24]^e[16];
e2[4]=e[32]^e[24];//后八位,连续8个1
cin>>T;
for(int p=1;p<=T;p++) {
f1.clear();
f2.clear();
ans.clear();
f2.insert(make_pair(0,0));//l=0一定标蓝,以防直接一次性全部标红
cin>>n;
if(n==0){//特判
cout<<"Case #"<<p<<":"<<el;
cout<<1<<el;
cout<<"0.0.0.0/0"<<el;
continue;
}
for(int i=1;i<=n;i++){
x=0;
scanf("%u.%u.%u.%u/%u",&a[3],&a[2],&a[1],&a[0],&l);
for(int j=0;j<=3;j++){//ip地址转换为 unsigned int
x+=a[j]<<(j*8);
}
work(x,l);//对区间标红,标蓝
}
solve(0,0);//(x=0,l=0)为根节点
cout<<"Case #"<<p<<":"<<el;
cout<<ans.size()<<el;
for(auto i:ans){
Pr(i);//打印答案
}
}
}