B 博弈

虽然不难但是很有教育意义。很大一类博弈问题的思路都是:某一个量每轮都可以变化某一固定值,当这个量达到某一阈值时游戏结束。那么不管怎么操作,游戏的胜负在开始时都已经注定了,唯一的影响就是先后手,只要判断下奇偶就行了。

对这题来说,每轮都变化的固定值是每轮数字个数都会减一,阈值是结束时数字一定是奇偶交替排列,奇偶数量相等,也就是总数一定是偶数。那么就很简单了,开始时数字个数如果是奇数,先手必胜,反之先手必败。

不过考场上并没有做出来,因为没看到数字是在一个环上的,所以说一定要认真读题……

GH 枚举

只有三个元素,完全可以把他们所有的大小关系全枚举出来,然后看是否存在一种满足题目给出的约束。

然而重点还是读题,Z等于1表示确定x严格小于y,等于0表示不确定x是否严格小于y,并不是确定x严格大于y!!!!x可能小于y,也可能等于y!!!

当时看题干太长就以为前面的都是废话,结果这个k=0/1的定义实际上只在前面特长的那段资料里提过一次,后面没有再给出形式化题意。

using namespace std;
const int N=1e3+10;
int x[N],y[N],z[N];
int main(){
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        bool flag=0;
        for(int i=1;i<=n;i++)cin>>x[i]>>y[i]>>z[i];
        for(int i=1;i<=3;i++)
            for(int j=1;j<=3;j++){
                for(int k=1;k<=3;k++){//枚举大小关系
                    int a[4];
                    a[1]=i;
                    a[2]=j;
                    a[3]=k;
//                     cout<<i<<' '<<j<<' '<<k<<' ';
                    bool flag2=1;
                    for(int p=1;p<=n;p++){//检验约束是否满足
                        if(z[p]==0){
                            if(a[x[p]]<a[y[p]]){
                                flag2=0;
                            }
                        }
                        else{
                            if(a[x[p]]>=a[y[p]]){
                               flag2=0;
                            };
                        }
                    }
//                     cout<<flag2<<'\n';
                    if(flag2)flag=1;
                }
            }
                
        if(flag)cout<<"Yes\n";//存在一组满足就可以
        else cout<<"No\n";
    }
}

J 期望,容斥

很多时候其实没想的那么难,关键是找到一些重要的性质。比如期望是可以线性叠加的,因此问总人数的期望,其实就是每个人成为心动嘉宾的期望的和,只需要单独计算每个人成为心动嘉宾的概率然后加起来。

一个人成为心动嘉宾,条件是所有他有好感的嘉宾中,至少有一个人选了他。用容斥来算就是所有他有好感的嘉宾都不选他的概率的反面。

using namespace std;
const int N=1e5+10;
const int mod=1e9+7;
#define ll long long
ll qpow(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1)ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
ll inv(ll x){
    return qpow(x,mod-2);
}
vector<int>a[N],b[N];
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=k;i++){
        int u,v;
        cin>>u>>v;
        a[u].push_back(v);
        b[v].push_back(u);
    }
    ll ans1=0;
    for(int i=1;i<=n;i++){
        ll t=1;
        for(int j:a[i]){//枚举所有有好感的嘉宾
            t=t*(1-inv(b[j].size())+mod)%mod;//这个人不选他的概率
        }
        ans1=(ans1+1-t+mod)%mod;//所有人都不选他的反面就是至少一个人选他
    }
    ll ans2=0;
    for(int i=1;i<=m;i++){
        ll t=1;
        for(int j:b[i]){
            t=t*(1-inv(a[j].size())+mod)%mod;
        }
        ans2=(ans2+1-t+mod)%mod;
    }
    cout<<"modint\n";
    cout<<ans1<<' '<<ans2;
}

k构造

这个思路确实不敢想,直接构造,最后能构造出来就是有,造不出来就是没有。

using namespace std;
const int N=1e6+10;
queue<int>qr,qb;
int ls[N],rs[N];
int tot,a,b,T;
bool build(queue<int>& in,queue<int>& out,int& cnt){
    if(cnt>=2&&!out.empty()){//还有至少两个叶节点的名额,并且还有可以父节点
        int x=out.front();
        out.pop();//取一个父节点出来
        ls[x]=++tot;//造两个叶节点,入队
        in.push(tot);
        rs[x]=++tot;
        in.push(tot);
        cnt-=2;//叶节点名额减二
        return 1;//构造成功
    }
    return 0;//反之构造失败
}
int main(){
    cin>>T;
    while(T--){
        cin>>a>>b;
        for(int i=1;i<=a+b;i++)ls[i]=rs[i]=-1;
        while(qr.size())qr.pop();
        while(qb.size())qb.pop();
        qb.push(1);//开始有一个黑色的根节点
        a--;//黑色名额减一
        tot=1;
        while(build(qb,qr,a)||build(qr,qb,b));//构造到不能构造
        if(!a&&!b){//如果两个颜色名额都刚好用完,构造成功
            cout<<"Yes\n";
            for(int i=1;i<=tot;i++)cout<<ls[i]<<' '<<rs[i]<<'\n';
        }
        else{//反之构造失败
            cout<<"No\n";
        }
    }
}

LM map统计数对,数论

统计数对,经典问题,首先应该考虑的思路就是单向遍历,每读一个元素就检测map里有多少可以和当前元素构成合法数对的元素,最后再把这个数也加入map

以上是一般性的思路,本题的特殊性在于要统计的是有多少数对,拼起来是36的倍数。这里又有两种思路,特殊一点的是36是4和9的倍数,我们可以从4和9倍数的特征入手,4的特征是最后两位是4的倍数,9的特征的是所有数位之和是9的倍数。一般性的思路是,假设这里的36是一个更一般的数字m,开map统计膜m的余数分别为0-m-1的元素的个数,然后没读一个元素,就遍历0-m-1,看哪些余数的元素可以和当前元素拼成膜m余0的数。

对于最后一步,假设当前读取的元素放在低位,map里取出的元素放在高位,那么实际上就是要判断(j*10^len(i)+i)%36是否为0。根据模运算的规则,可以转化为(j*(10^len(i)%36)+i)%36,j和i都不大,唯一的问题是这个10^len(i)%36,注意到她只和长度有关,和具体是什么数无关,可以预处理。当然这里还有一个特殊的性质,就是10大于等于2次幂膜36都为28,因此其实10^len(i)%36只有两种可能,10是10,100及以上是28

using namespace std;
#define ll long long
vector<ll>a;
int main(){
    ll ans=0;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        ll t;
        cin>>t;
        a.push_back(t);
    }
    auto cal=[&](){
        ll cnt[40]={0};
        for(auto i:a){//i作为右边的数
            for(int j=0;j<36;j++){//枚举所有左边的数
                if((j*(i<10LL?10LL:28LL)+i)%36==0){//判断拼起来是否是36倍数
                    ans+=cnt[j];//是的话加上贡献
                }
            }
            cnt[i%36]++;//当前i也加入map
        }
    };
    cal();
    reverse(a.begin(),a.end());//前面是被枚举的数只能在右边,漏了一些情况
    cal();//数组反过来,再算一遍
    cout<<ans;      
}