2014-2015 ACM-ICPC, Asia Xian Regional Contest(部分题解)

原题链接

点这里

A题(签到)

K题(辗转相减数列)

题目

在这里插入图片描述

算法思路

  1. 类似辗转相减法,找规律
  2. 可以证明,s1,s2交换后,答案不变(虽然我们证明不了)(训练的时候,杰神打了一个暴力,来验证)
  3. 所以这样就可以只考虑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)
  4. 最后在特判一下边界条件有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种颜色,且相同颜色不能相邻的方案数,结果取模

算法思路

  1. 容斥原理
  2. 用2-k种颜色的方案数,第一个位置有k种颜色的填法,后续的每个位置,为了不跟前一个位置相同,都有(k-1)种颜色的填法,但是其中有2-(k-1)颜色的方案,所以要容斥原理扣除掉
  3. 容斥原理得: 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(k1)n1Ckk1(k1)×(k2)n1+Ckk2×(k2)(k3)n1+
  4. 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)kiCkii(i1)n1
  5. 代码为
	    for(int i=k;i>=2;i--){
            ans=(ans+op*C(k,i)*i%p*ksm(i-1,n-1)%p)%p;
            op=-op;
        }
  1. 训练的时候一直叫杰神放弃,因为分析了一波感觉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的前缀的所有子网

算法思路

  1. 给区间设立三种颜色,如果当前区间为红色,则表示当前子网已经全部被覆盖了直接返回
  2. 当前区间为蓝色,表示当前区间有子网,不能直接标红,需要继续递归儿子节点
  3. 区间为白色的时候,表示当前区间没有子网,可以直接标红
  4. 我们最终的目标就是要将所有的子网全部标红,即以(x=0,l=0)为根节点的树,的根节点标红即可
  5. 其次是细节问题,其实可以开long long,但是32位完全可以用unsigned int,因为int左移的话,符号位不会变,l=1位为1,就始终是负数,所以采用unsigned int
  6. 读入的话,借助scanf来读入,斜杠不用转移……反斜杠才需要转移,所以此处输出不用转移
  7. 输出的话同理,将u int转换为IP地址即可,所以此处输入输出要用预处理数组e[],e2[],方便直接取出任意8位
  8. n==0的时候要特判一下,还有初值问题,记得将f1,f2,ans给clear掉
  9. 训练的时候,题目都读了好久,快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);//打印答案
        }
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值