2014 多校赛 第一场

时间:2023-01-09 09:29:47

题目链接

A - Couple doubi

题意:桌上共有 k 个球,第i个球的值为 (1^i+2^i+...+(p-1)^i )mod p
DouBiXp 和 他的女朋友 DouBiNan 轮流拿球,DouBiNan先拿,
所有的球都拿完后,谁手上球的值总和更大谁就赢,
已知 k,p,且p为素数,
若DouBiNan赢输出"YES",否则输出"NO"

分析:DouBiNan先拿,为了赢肯定先拿没有被拿的球中值最大的,
找规律得 每个球的值要么为 0,要么为某个的正数x,且每p-1个球有一个的值为x
那么如果值为x的球个数如果为奇数,则DouBiNan赢,否则赢不了

#include<stdio.h>
int main()
{
    int p,n;
    while(scanf("%d%d",&n,&p)!=EOF){
        if((n/(p-1))%2)
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}


D - Task
题意:有n台机器和m个任务,第i个任务需要xi时间去完成,且它的难度等级为yi
若第i个任务被完成,可获得收益 500*xi+2*yi
每台机器有一个最大工作时间和能完成的最大难度等级,
若某台机器要完成某任务,则机器的工作时间要不低于任务需要的时间,
机器的难度等级不低于任务的难度等级,
一台机器一天只能完成一次任务,且一个任务只能由一台机器完成

已知每台机器和每个任务的完成时间和难度等级,
求一天能完成的最多的任务数,在此前提下的最大收益

分析:贪心求解
收益只有完成的任务的x,y有关,且x,y越大,获得的收益越大
所以要优先完成x更大的任务,若x相等,要优先完成y大的任务
即任务要按x从大到小排序,若x相等则按y从大到小排序
任务的x按从大到小排序,再给任务匹配机器

当有多台机器符合x条件,那么要选择y满足条件的最小的y,
这样没被用的更大的y的机器,更可能符合完成其他任务

#include<stdio.h>
#include<algorithm>
#define N 100000
using namespace std;
struct stu{
    int time,level;
}task[N+10],machine[N+10];
int cmp(stu a,stu b){
    if(a.time==b.time)
        return a.level>b.level;
    return a.time>b.time;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++)
            scanf("%d%d",&machine[i].time,&machine[i].level);
        for(int i=1;i<=m;i++)
            scanf("%d%d",&task[i].time,&task[i].level);
        sort(machine+1,machine+1+n,cmp);
        sort(task+1,task+1+m,cmp);
        int j=1,cnt[105]={0},maxm=0;
        long long ans=0;
        for(int i=1;i<=m;i++){
            while(j<=n&&task[i].time<=machine[j].time){
                cnt[machine[j].level]++;
                j++;
            }
            for(int k=task[i].level;k<=100;k++){
                if(cnt[k]){
                    maxm++;
                    ans+=task[i].time*500+task[i].level*2;
                    cnt[k]--;
                    break;
                }
            }
        }
        printf("%d %I64d\n",maxm,ans);
    }
    return 0;
}


E - Peter's Hobby

题意:已知昨天天气与今天天气状况的概率关系(wePro),和今天天气状态和叶子湿度的概率关系(lePro)
第一天为sunny 概率为 0.63,cloudy 概率 0.17,rainny 概率 0.2.
给定n天的叶子湿度状态,求这n天最可能的天气情况

分析:概率dp
设 dp[i][j] 表示第i天天气为j的最大概率,
pre[i][j]表示第i天天气最可能为j的前一天天气,
dp[i][j]=max(dp[i-1][k]+log(wePro[k][j])+log(lePro[j][lePos[i]])) (k=0,1,2 表示昨天的天气)

注:由于概率越乘越小,考虑精度原因,用log取对数
log(a*b*c) = log a + log b +log c

#include<stdio.h>
#include<string.h>
#include<math.h>
char Con[5][10]={"Dry","Dryish","Damp","Soggy"};
char We[5][10]={"Sunny","Cloudy","Rainy"};
double lePro[3][4]={0.6,0.2,0.15,0.05,
                  0.25,0.3,0.2,0.25,
                  0.05,0.10,0.35,0.50};  //叶子
double wePro[3][3]={0.5,0.375,0.125,
                    0.25,0.125,0.625,
                    0.25,0.375,0.375};   //天气
double init[3]={0.63,0.17,0.2};
double dp[55][3],pre[55][3];
int n,lePos[55];
void solve()
{
    for(int i=0;i<3;i++)
        dp[1][i]=log(init[i])+log(lePro[i][lePos[1]]);
    for(int i=2;i<=n;i++){
        for(int j=0;j<3;j++){  //今天天气
            double maxp=-1e8;
            int pos=0;
            for(int k=0;k<3;k++){  //昨天天气
                double temp=dp[i-1][k]+log(wePro[k][j])+log(lePro[j][lePos[i]]);
                if(temp>maxp){
                    maxp=temp;
                    pos=k;
                }
            }
            dp[i][j]=maxp;
            pre[i][j]=pos;
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    for(int k=1;k<=T;k++){
        printf("Case #%d:\n",k);
        scanf("%d",&n);
        char con[10];
        for(int i=1;i<=n;i++){
            scanf("%s",con);
            for(int j=0;j<4;j++)
                if(strcmp(con,Con[j])==0){
                    lePos[i]=j;
                    break;
                }
        }
        solve();
        double maxp=-1e8;
        int ans[55];
        for(int i=0;i<3;i++)
        if(dp[n][i]>maxp){
            maxp=dp[n][i];
            ans[n]=i;
        }
        for(int i=n-1;i>=1;i--)
            ans[i]=pre[i+1][ans[i+1]];
        for(int i=1;i<=n;i++)
            printf("%s\n",We[ans[i]]);
    }
    return 0;
}

资料扩展:详情点此
本题属于 隐马尔可夫模型
马尔可夫模型:统计模型,每个状态只依赖于之前的状态

马尔可夫模型可用马尔可夫过程描述
我们就为上面的一阶马尔科夫过程定义了以下三个部分:
  状态:晴天、阴天和下雨
  初始向量:定义系统在时间为0的时候的状态的概率
  状态转移矩阵:每种天气转换的概率
  所有的能被这样描述的系统都是一个马尔科夫过程。

隐马尔可夫模型 (Hidden Markov Model) 是一种统计模型,用来描述一个含有隐含未知参数的马尔可夫过程。
包含隐藏状态 (如:天气状态)和 可观状态(如:叶子的湿度)

可以观察到的状态序列和隐藏的状态序列是概率相关的


I - Turn the pokers

题意:有m张牌,初始状态都是正面朝下,现在要进行n次操作,
第i次操作要翻ai张牌,求n次操作后,牌的状态可能有多少种?


分析:牌只有两种状态,设面朝下为0,面朝上为1,则初始状态都为0
很显然每一张牌的状态只跟这张牌被翻的次数的奇偶性有关。
翻牌i次后1的个数的奇偶性一定与前i次翻牌的总张数 a1+a2+...+ai的奇偶性相同
(初始状态1的个数为0,是偶数,1.若翻偶数张牌,1的个数还是偶数
2.若翻奇数张,得到的还是奇数,
在第2种情况下若再翻奇数次,可能会翻奇数个0,偶数个1,或者偶数个0,奇数个1,
结果还是奇数,而两次翻牌张数和为偶加奇,也是奇数。。。可自行证明)

若终态有最少有min个1,最多有max个1,那么1的个数可能为 min,min+2,min+4,...,max
即牌的状态可能有 C(m,min)+C(m,min+2)+C(m,min+4)+...+C(m,max)
这样就转化为求min,max及组合数了
C(m,i)=m!/(i!*(m-i)!),因为最终要取余,而阶乘数太大存不了,还涉及到除法,
直接取余再除肯定不行(同余定理不适用于除法)
可以利用费马小定理解决:
由定理得:a^(p-1)≡1%p
则 a^(p-2)≡1/a%p
因为p很大,需要用快速幂取余来做

#include<stdio.h>
#define N 100000
#define mod 1000000009
#define LL long long
LL fact[N+10];
void init()
{
    fact[0]=1;
    for(int i=1;i<=N;i++)
        fact[i]=fact[i-1]*i%mod;
}
LL powMod(LL a,LL b)
{
    LL ans=1;
    a%=mod;
    while(b){
        if(b%2)
            ans=ans*a%mod;
        b/=2;
        a=a*a%mod;
    }
    return ans;
}
int main()
{
    int n,m;
    init();
    while(scanf("%d%d",&n,&m)!=EOF){ //m张扑克,n次操作
        int min1,max1,tempMin=0,tempMax=0; //1的最小最大个数
        while(n--){
            int x;
            scanf("%d",&x);
            //计算最少的1,要尽可能让1->0
            if(x<=tempMin)
                min1=tempMin-x;
            else if(x<=tempMax){
                if((x%2)==(tempMin%2))
                    min1=0;
                else
                    min1=1;
            }
            else
                min1=x-tempMax;
            //计算最多的1,要尽可能让0->1
            if(x<=m-tempMax)
                max1=tempMax+x;
            else if(x<=m-tempMin){
                if((x%2)==((m-tempMin)%2))
                    max1=m;
                else
                    max1=m-1;
            }
            else
                max1=m-(x-(m-tempMin));
            tempMin=min1;
            tempMax=max1;
        }
        LL ans=0;
        for(int i=min1;i<=max1;i+=2) //费马小定理
            ans=(ans+(fact[m]*powMod(fact[i]*fact[m-i],mod-2))%mod)%mod;
        printf("%I64d\n",ans);
    }
    return 0;
}



G - Xor
题意:有n个数 (a0,a1,..,an),
f(x)= 满足 b1 xor b2 xor ... xor bn = x 的个数 (0<=bi<=ai)
要进行m次操作
Q x, 求 f(x)
C x y,将第x个数 即 ax 修改为 y

分析:对于这种多查询多维护的题可以用线段树求解
线段树:
是一种擅长处理区间的数据结构。
对其的查找和维护的时间复杂度都是O(log n).
未优化的空间复杂度为 2n

若a0=100101 (二进制)
则b0 =0***** 或 1000** 或 100100 或 100101  (0<=b0<=a0)
将b中确定的位称为前缀,不确定的位(为0或1)称为后缀
则 线段树中结点需要 包含 前缀的值,后缀位数,及组成该形式的数的个数
前缀最大到 1000 (十进制),后缀 最多为 10 ,可以用一个int存这两个信息
因为 10用 4位二进制就可表示,可以用后4位存后缀位数
组成该形式的数的个数 也可用 一个int即可
而b有多种可能形式,且个数未知
所以线段树的每个结点 可用 vector<pair<int,int> > 存储

子树根结点由左右孩子异或得到,将左右孩子可能的形式的数两两异或
可求子根结点,最终可得根结点(b1 xor b2 xor ... xor bn)每种情况的个数
求f(x) 即求根结点 每种情况能组成与x的相等的值的总和
每种情况是否能组成与x相等的值,即看前缀的值与x的前缀的值是否相等

#include<cstdio>
#include<vector>
#include<map>
#define N 20000
#define mod 1000000007
using namespace std;
int a[N+10],minlen;
vector<pair<int,int> > tree[2*N+10];
vector<pair<int,int> > creat_node(int val)
{
    vector<pair<int,int> >temp;
    for(int i=0;i<10;i++){ //val即a[i]最大1000,最多由10位二进制组成
        if(val&(1<<i)){  //判断val第i位是否为1
            int first=(((val>>i)^1)<<4)|i;  //保留从第i位开始的前缀,并将第i位的1变成0
         //且前缀后面添加4位,用来存后缀的位数,后缀位数最多为10,由四位二进制可表示
            temp.push_back(make_pair(first,1));
            //pair的second元素用来存组成上诉形式的数的个数
        }
    }
    temp.push_back(make_pair(val<<4,1));  //与val相等,则前缀为val,后缀位数为0
    return temp;
}
int Xor(int valL,int valR)
{
    int lenL=valL&0xf,lenR=valR&0xf;
    if(lenL>lenR){
        swap(lenL,lenR);
        swap(valL,valR);
    }
    valL>>=4;
    valR>>=4;
    valL>>=(lenR-lenL);
    minlen=lenL;
    return (valL^valR)<<4|lenR;
}
void pushUp(int node)     //根据左右孩子求根节点
{
    map<int,int> m;
    tree[node].clear();
    int lenL=tree[2*node].size();
    int lenR=tree[2*node+1].size();
    int cnt=0;
    for(int i=0;i<lenL;i++){
        for(int j=0;j<lenR;j++){
            int first=Xor(tree[2*node][i].first,tree[2*node+1][j].first);
            long long numL=tree[node*2][i].second;
            long long numR=tree[node*2+1][j].second;
            int second=numL*numR%mod*(1<<minlen)%mod;
            if(m.find(first)==m.end()){
                m[first]=cnt++;
                tree[node].push_back(make_pair(first,second));
            }
            else{
                int pos=m[first];
                tree[node][pos].second=(tree[node][pos].second+second)%mod;
            }
        }
    }
}
void build(int l,int r,int node)      //建树
{
    if(l==r){
        tree[node]=creat_node(a[l]);
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,node*2);
    build(mid+1,r,node*2+1);
    pushUp(node);
}
void update(int l,int r,int node,int pos,int val)  //更新维护树
{
    if(l==r){
        tree[node]=creat_node(val);
        return;
    }
    int mid=(l+r)>>1;
    if(mid>=pos)
        update(l,mid,node*2,pos,val);
    else
        update(mid+1,r,node*2+1,pos,val);
    pushUp(node);
}
int query(int x,int node)            //查找
{
    int ans=0,num=tree[node].size();
    for(int i=0;i<num;i++){
        int first=tree[node][i].first;
        int len=first&0xf;
        first>>=4;
        if(first==(x>>len))
            ans=(ans+tree[node][i].second)%mod;
    }
    return ans;
}
int main()
{
    int T,n,m;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        build(1,n,1);

        while(m--){
            char ope[3];
            scanf("%s",ope);
            if(ope[0]=='Q'){
                int x;
                scanf("%d",&x);
                int ans=query(x,1);
                printf("%d\n",ans);
            }
            else{
                int x,y;
                scanf("%d%d",&x,&y);
                update(1,n,1,x+1,y);
            }
        }
    }
    return 0;
}