CSP-S模拟14

A. 莓良心

引用题解:考虑什么时候答案是0,显然是maxl<=minr。如果答案不是0,显然可以把值域缩小到[minr,maxl]之间,取到minr,maxl的两个区间变成了单点,把它们删除递归下去即可。

maxl相当于最小下界,minr相当于最大上界,所以每一次区间缩小相当于把某一段区间代表的数变成了定值(maxl对应的区间取l,minr对应的区间取r)。可以建立两个堆来找到最值,关于堆的删除操作:既然每次是确定了一个区间,为什么ql和qr可以独立删除?(为什么maxl删除时不把它对应的r从qr里拿走?)先考虑maxl,它对应的r留在堆里会影响答案的情况只有在maxl <= minr之前把它取出,第一次maxl已经被删了,它对应的r比它大,那么在未来取出这个r作为最小值的时候贡献一定为0,把这个r删掉的结果是取出来更大的r,贡献还是0,只要还有比这个r小的存在它就不会被取出,就不会影响答案。

记录答案就是当前区间上界-下界加上内部的数同时与上下界的差值,设sz为区间内包括的x的数目,ans += (l-r)*(sz+1)

code
//正青春的年华,就是应该献给直指星辰的梦想啊!
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 3e6 + 3;
const ll mod = 998244353;

priority_queue<int> ql;
priority_queue<int, vector<int>, greater<int> > qr;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    int n = read();
    for(int i=1; i<=n; i++)
    {
        ql.push(read()); qr.push(read());
    }
    ll ans = 0; int l = 1e8, r = 0;
    for(int i=1; i<n; i++)
    {
        l = min(l, ql.top()); ql.pop();
        r = max(r, qr.top()); qr.pop();
        if(l <= r) break;
        ans += 1ll * (l-r) * (n-i*2+1);
    }
    printf("%lld\n", ans);
    
    return 0;
}

 

B. 尽梨了

全场切了!?

再给我100年青春岁月可能才有水平切这种T……

prei表示r <= i的位置的并,得到这个并的这些行必然选0,那么1一定来源与列;nxti表示r > i的位置的并,得到这个并的这些行必然选1,那么0一定来源于列。

        //在prei中为1表示强制取1,为0没有限制
        //在nxti中为0表示强制取0,为1没有限制
        //所以nxti中为1prei才可以为1但不一定是1,nxti中为0prei一定不能选

以上解释pre[i] | nxt[i] 结果是nxt[i].

谐音不错——我尽力了***

code
//正青春的年华,就是应该献给直指星辰的梦想啊
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5005;
const ll mod = 998244353;//我要是再取错mod我就**

int n, fac[maxn], inv[maxn], p2[maxn], ans;
bitset<maxn> s[maxn], pre[maxn], nxt[maxn];
vector<int> v[maxn];
char c[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

ll qpow(ll a, ll b)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

int C(int n, int m)
{
    if(n < m || m < 0) return 0;
    return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        scanf("%s", c+1);
        //预处理每一行1的个数
        for(int j=1; j<=n; j++) if(c[j] == '1') s[i].set(j);
        v[s[i].count()].push_back(i);
    }
    inv[0] = fac[0] = 1; for(int i=1; i<=n; i++) fac[i] = 1ll*fac[i-1]*i%mod;
    inv[n] = qpow(fac[n], mod-2); for(int i=n-1; i>0; i--) inv[i] = 1ll*inv[i+1]*(i+1)%mod;
    p2[0] = 1; for(int i=1; i<=n; i++) p2[i] = (p2[i-1]+p2[i-1])%mod;
    for(int i=1; i<=n; i++)
    {
        pre[i] = pre[i-1];
        for(int x : v[i]) pre[i] |= s[x];//r<=i的1的位置的并
    }
    for(int i=1; i<=n; i++) nxt[n+1].set(i);
    for(int i=n; i>=1; i--)
    {
        nxt[i] = nxt[i+1];
        for(int x : v[i]) nxt[i] &= s[x];//r>=i的0的位置的并
    }
    //枚举要求i列为1
    for(int i=0; i<=n; i++)
    {
        //在prei中为1表示强制取1,为0没有限制
        //在nxti中为0表示强制取0,为1没有限制
        //所以nxti中为1prei才可以为1但不一定是1,nxti中为0prei一定不能选1
        if((pre[i] | nxt[i]) == nxt[i])
        {
            ans += p2[v[i].size()]-1; ans %= mod;//减掉2^0,只有1种情况的加一次就够了
            //合并为乘法之后可以省略这种讨论,上面是有相等的情况
            int c1 = pre[i].count(), c2 = nxt[i].count();
            //没有r=a的情况,所有的都被固定了,i-c1=0
            ans += C(c2-c1, i-c1); ans %= mod;
            //如果没有相等的,n-c1-(n-c2)是没有被确定的位置,c1个必选1,现在有i个1
            //剩下的1可以在没有规定必选0的位置和已经填上来满足c1的位置随便填
        }
    }
    printf("%d\n", ans);
    
    return 0;
}

 

C. 团不过

用总方案数减掉异或和为0的方案数得到答案。

得到异或和为0的方案数:无论i-1异或的结果是什么,都能加入一个i使异或结果为0,有两种不合法的情况:前i-1堆异或为0,由于i不能选0,要减掉;新加入一对石子和之前的某堆重复了,不用考虑到底是和谁重复,两个相同的数异或为0,i和i-1中的任意一堆相同,必然导致剩下的i-2堆异或为0,再乘上第i-1堆可选的值域,前面是和i-1中的哪一堆有重复。

code
//正青春的年华,就是应该献给直指星辰的梦想啊!
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e7 + 3;
const ll mod = 1e9 + 7;

ll n, p[maxn], p2[maxn], f[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    n = read();
    p2[0] = 1; for(int i=1; i<=n; i++) p2[i] = p2[i-1]*2%mod;
    p[0] = 1; for(int i=1; i<=n; i++) p[i] = p[i-1]*(p2[n]-i)%mod;//总数,就是个全排列
    for(int i=3; i<=n; i++) 
    {
        f[i] = (p[i-1]-f[i-1]-(i-1)*f[i-2]%mod*(p2[n]-i+1)%mod+2*mod)%mod;
    }
    printf("%lld\n", (p[n]-f[n]+mod)%mod);
    
    return 0;
}

 

D. 七负我

一个不属于完全图中的点如果被删掉不影响答案,所以就是统计最大完全子图的大小,然后平均分配。

好像平分和最大完全子图都由"完全不归纳法"得出???

求完第二部分的f不要跑,记得把子图中的大小也加上,因为统计的时候用的是最大值。

code
//正青春的年华,就是应该献给直指星辰的梦想啊
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 50;
const ll mod = 1e9 + 7;
const double inf = 1e12;

#define count __builtin_popcount
ll n, m, m1, m2, all, Max, e[maxn], f[1<<20];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    n = read(); m = read(); all = read(); m1 = n>>1, m2 = n-m1;
    for(int i=1; i<=m; i++)
    {
        int x = read(), y = read(); 
        e[x] |= 1ll<<y-1; e[y] |= 1ll<<x-1;
    }
    for(int sta=1; sta<(1ll<<m1); sta++)
    {
        ll sum = count(sta); if(sum <= Max) continue;
        for(int i=1; i<=m1; i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto X;
        Max = max(Max, sum); X:;
    }
    for(int sta=1; sta<(1ll<<m2); sta++)
    {
        //printf("-----sta---%d\n", sta);
        ll sum = count(sta); 
        for(int i=1; i<=m2; i++) 
        {
            if((sta>>i-1)&1) 
            {
                if(((e[i+m1]>>m1)&sta)!=(sta^(1ll<<i-1))) 
                {
                    //printf("%d is illegal!\n", i);
                    goto Y;
                }
            }
        }
        //printf("sta---%d\n", sta);
        f[sta] = sum; Max = max(Max, sum); Y:;
    }
    //for(int i=1; i<=m2; i++) f[1ll<<i-1] = 1;
    for(int sta=1; sta<(1ll<<m2); sta++)
    {
        ll U = (1ll<<m2)-1, sum = f[sta];
        for(int i=1; i<=m2; i++) if((sta>>i-1)&1) U &= e[i+m1]>>m1;
        for(int i=1; i<=m2; i++)
        {
            if(((sta>>i-1)&1)^1)
            {
                int tmp = (U>>i-1)&1;
                //tmp == 0但是sum == 1,还是要加上,也就是我这种状态虽然不合法
                //但是它包含了多少合法的全都算数
                //这里相当于求了一个前缀和
                if(f[sta|(1ll<<i-1)] >= sum+tmp) continue;
                f[sta|(1ll<<i-1)] = sum + tmp;
            }
        }
    }
    for(int sta=1; sta<(1ll<<m1); sta++)
    {
        ll U = (1<<m2)-1, sum = count(sta);
        for(int i=1; i<=m1; i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto Z;
        for(int i=1; i<=m1; i++) if((sta>>i-1)&1) U &= e[i]>>m1;
        //因为这里留的是第二类的最大可行点数(和第一部分中的点都有连边),应该用前缀和
        Max = max(Max, sum+f[U]); Z:;
    }
    double ans = (1.0*(Max*(Max-1)/2)*(1.0*all)/(1.0*Max)*(1.0*all)/(1.0*Max));
    printf("%.6lf\n", ans);
    
    return 0;
}

code--dfs
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 105;
const ll mod = 1e9 + 7;

int n, m, k, Max, s[maxn];
bool a[maxn][maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

void dfs(int x, int res)
{
    if(res == Max) 
    {
        printf("%.6lf", (double)k*k/res*(res-1)/2);
        exit(0);
    }
    if(x == n+1) return;
    dfs(x+1, res);
    for(int i=1; i<=res; i++) if(!a[x][s[i]]) return;
    s[++res] = x;
    dfs(x+1, res);
}

int main()
{
    n = read(); m = read(); k = read();
    for(int i=1; i<=m; i++)
    {
        int x = read(), y = read();
        a[x][y] = a[y][x] = 1;
    }
    for(int i=n; i>=2; i--) Max = i, dfs(1, 0);
    
    return 0;
}

 

对结论的证明(完全图):设u,v为互不相连的两点,设su,sv分别为与u,v相连的点权和,那么u,v的总贡献应该是1/2*(su*tu+sv*tv),由于我们可以决定点权的分配,当构造su==sv时可保证u,v其中一点不选也是一种最优解,因此两个不连通的点间总有一点不会被分配权值,最终答案一定会出现在完全图中。

(平均分配)设ti为团中第i个点的权值,s为团大小,则x = 团中每个节点ti求和,ans = ∑u!=v tu*tv = (x^2-∑t=1,s ti^2)/2,由均值不等式得当ti都相等时ans取最大值。

(最大)每个点t = x/k,答案为x^2*k*(k-1)/(2*k^2),它关于k递增

posted @ 2022-09-29 14:42  Catherine_leah  阅读(26)  评论(0编辑  收藏  举报
/* */