牛客比赛订正(3,4) 2021牛客暑期多校训练营4

 2021牛客暑期多校训练营3

2021.7.28
[J Counting Triangles]
题目一看大概就知道是三元环计数了。就是记有序三元组(a,b,c)(a<b<c),且a->b,b->c,c->a都有边的三元组的个数。至于网上找到的普通情况下的三元环计数问题过不了的原因是:这个题本身的特点,这个是无向完全图。每两个点之间都有边。完全图自然有完全图的性质,若啥都不管直接用最普遍的情况去写得话肯定是会T掉的。
接下来思考在这个完全图中怎么找三边相同的三圆环,每个边要么是白色,要么是黑色。考虑图中存在的三圆环,要么是全黑,全白(我们定义为同色三元环),要么是有黑有白,根据抽屉原理,三个边中一定有两条边颜色相同,我们暂且定义为异色三元环。发现整个图中所有的三元环也就这两种。而总的三元环的个数为$C_n^3$.考虑同色三元环和异色三元环对答案的贡献。我们每次枚举中间节点,然后找它的前驱和后继,由于考虑到同色三元环的话,前驱和后继之间的边的颜色不好确定,所以考虑异色三元环,我们只需要保证前驱与中间节点,后继与中间节点的颜色不同,那么它一定是一个异色三元环。所以我们考虑只统计异色三元环的数量。由于异色三元环有两个节点的两边颜色不同,所以同一个异色三元环会被我们统计两次,我们/2就得到了答案。

```
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll cnt[8005][3];

namespace GenHelper
{
unsigned z1,z2,z3,z4,b,u;
unsigned get()
{
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
bool read() {
while (!u) u = get();
bool res = u & 1;
u >>= 1; return res;
}
void srand(int x)
{
z1=x;
z2=(~x)^0x233333333U;
z3=x^0x1234598766U;
z4=(~x)+51;
u = 0;
}
}
using namespace GenHelper;
bool edge[8005][8005];
int main() 
{
//    freopen("1.in","r",stdin);
int n,seed;
cin >> n >> seed;
srand(seed);
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
edge[j][i] = edge[i][j] = read();
cnt[i][edge[i][j]]++;
cnt[j][edge[i][j]]++;
}
ll ans=1ll*n*(n-1)*(n-2)/6,cnt1=0;
for(int i=0;i<n;++i) cnt1+=cnt[i][0]*cnt[i][1];
cout<<ans-cnt1/2<<endl;
return 0;
}
```
View Code

2021.7.28

F Just a joke

博弈论的题目,假如说没有可以删去一颗树的操作,我们直接判边是奇是偶给输赢就行了。但现在加上了删去一颗树的操作,考虑树的特性,点数为n的话,边数为n-1,两者相加的话...一定是奇数!考虑着个博弈的最后结果一定是将图中的所有边所有点都移除,也就是说我们单独的删一个点,或者删一个边,他们一次操作的代价也是奇数,与删除一棵树的代价等效,换句话说删去一棵树,并不影响我们每次操作后奇偶得变化。那我们直接n+m判奇偶就行了啊。

//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=210;
int n,m,vis[N],cb,cd,link[N],tot;
struct bian{int next,y;}a[N<<2];
 
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}

 
int main()
{
//  freopen("1.in","r",stdin);
    get(n);get(m);
    if((n+m)%2==0) cout<<"Bob"<<endl;
    else cout<<"Alice"<<endl;
    return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
View Code

J Average

题目要求找一个子矩阵,对子矩阵的长度和宽度有一个最小限度的要求,要求其子矩阵的平均值最大。并且本题子矩阵的生成是有两个序列生成的,w[i][j]=a[i]+b[j]..

我们很容易想到子矩阵都与a和b序列有关,自然而然的就能想到就其都写成a和b的形式。

我们考虑一个从w[1][1]到w[3][3]的子矩阵,可以写成这样:

a[1][1]   a[1][2]    a[1][3]                              a[1]+b[1]  a[1]+b[2]   a[1]+b[3]

a[2][1]   a[2][2]    a[2][3]             -->             a[2]+b[1]  a[2]+b[2]   a[2]+b[3]  

a[3][1]   a[3][2]    a[3][3]                               a[3]+b[1]  a[3]+b[2]   a[3]+b[3]

这个子矩阵的平均值也就是(a[1]*3+a[2]*3+a[3]*3+b[1]*3+b[2]*3+b[3]*3)/9,我们平均下去就是(a[1]+a[2]+a[3])/3+(b[1]+b[2]+b[3])/3,最后我们发现子矩阵的平均值就是两个序列中一段连续子序列的平均值相加。

这样之后问题就会转化为在一个序列中找到长度>=L的一个连续子序列使得其平均值最大。这是一个经典的二分的问题,我们可以二分一个平均值,然后将序列中的每个数都-mid,剩下的就是找序列中有没有一个连续的序列其和>0,直接扫一遍就行了。

//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
const db eps=1e-10;
int n,m,sl,sh;
db a[N],b[N],sum[N],c[N];
 
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
 
int main()
{
//  freopen("1.in","r",stdin);
    get(n);get(m);get(sl);get(sh);
    rep(i,1,n) get(a[i]);
    rep(i,1,m) get(b[i]);
    db ans=0;
    db l=1e-6,r=1e6;
    while(r-l>eps)
    {
        db mid=(l+r)/2;
        rep(i,1,n) c[i]=a[i]-mid;
        rep(i,1,n) sum[i]=sum[i-1]+c[i];
        db da=-1e10;
        db min_val=1e10;
        rep(i,sl,n)
        {
            min_val=min(min_val,sum[i-sl]);
            da=max(da,sum[i]-min_val);
        }  
        if(da>=0) l=mid;
        else r=mid;
    }
    ans=l;
    l=1e-6,r=1e6;
    while(r-l>eps)
    {
        db mid=(l+r)/2;
        rep(i,1,m) c[i]=b[i]-mid;
        rep(i,1,m) sum[i]=sum[i-1]+c[i];
        db da=-1e10;
        db min_val=1e10;
        rep(i,sh,m)
        {
            min_val=min(min_val,sum[i-sh]);
            da=max(da,sum[i]-min_val);
        }  
        if(da>=0) l=mid;
        else r=mid;
    }
    ans+=l;
    printf("%.10lf",ans);
    return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
View Code
题目要求构造三个字符串,我们直接去他们中最小的数用a当做他们共同的元素。这样一个为0,剩下的两个我们用b与c分别代替防止在重复即可.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1005;
int n,a,b,c,s1,s2,s3; 
char ch[5][N];
int main() 
{
    //freopen("1.in","r",stdin);
    cin>>a>>b>>c>>n;
    if(a<=b&&a<=c)
    {
        for(int i=1;i<=a;++i) ch[1][i]=ch[2][i]=ch[3][i]='a';
        s1=s2=s3=a;
        b-=a;c-=a;
        for(int i=1;i<=b;++i) ch[2][++s2]=ch[3][++s3]='b';
        for(int i=1;i<=c;++i) ch[1][++s1]=ch[3][++s3]='c';
    }
    else if(b<=a&&b<=c)
    {
        for(int i=1;i<=b;++i) ch[1][i]=ch[2][i]=ch[3][i]='a';
        s1=s2=s3=b;
        a-=b;c-=b;
        for(int i=1;i<=a;++i) ch[1][++s1]=ch[2][++s2]='b';
        for(int i=1;i<=c;++i) ch[1][++s1]=ch[3][++s3]='c';
    }
    else if(c<=a&&c<=b)
    {
        for(int i=1;i<=c;++i) ch[1][i]=ch[2][i]=ch[3][i]='a';
        s1=s2=s3=c;
        a-=c;b-=c;
        for(int i=1;i<=a;++i) ch[1][++s1]=ch[2][++s2]='b';
        for(int i=1;i<=b;++i) ch[2][++s2]=ch[3][++s3]='c';
    }
    if(s1>n||s2>n||s3>n) 
    {
        puts("NO");
        return 0;
    }
    for(int i=s1+1;i<=n;++i) ch[1][i]='d';
    for(int i=s2+1;i<=n;++i) ch[2][i]='e';
    for(int i=s3+1;i<=n;++i) ch[3][i]='f';
    for(int i=1;i<=3;++i) printf("%s
",ch[i]+1);
     return 0;
}
View Code

 E Tree Xor

题目大意:给定一棵树,树上每个边都有权值,且权值等于其连接的两个点的权值异或。现给出每个边的权值,和每个点权值的范围,问有多少种合法的方案使得每个点的权值在其范围之内并且每个边相连的两个点的权值异或等于边的权值?

首先思考现在边的权值我们是有的,只要任意确定任意一个节点的权值那与他相邻的节点的权值也依次确定,一直外推,整个树所有的权值都可以确定下来。既然如此我们就只考虑一号节点,对于l[1]-r[1]之间的所有数,有些数可能是违法的,(根据这些数确定的某些节点可能超出给定的范围)。现在我们要做的就是将违法的数给删掉,但每个节点的确定都是和相邻的节点确定的,我们如何通过与一号节点的关系来判断是否合法?我们不妨以1号节点为根,考虑确定一个节点x的过程,一号节点的值确定后,暂且为v,从1号节点到x节点的路径所有的边都要异或才能得出x的值,那我们完全用一个数组b[i]表示i节点到1号节点经过的所有边的异或值,待我们确定一号节点后直接v^b[i]就是i号节点的权值。这样之后我们就把问题转化成:给定一个序列,求问在l-r之间有多少个数,使得他们在异或b[i]之后的值在l[i]和r[i]之间。既然是异或肯定和二进制有关系,我们考虑将b[i],l[i],r[i]都用二进制表示出来,我们可以将异或b[i]之后大于r[i]的或小于l[i]的数(x)标记下,之后直接扫一下l[1]到r[1],有标记的不计入答案,就可以得出答案了。具体的怎么标记,我们从高到低依次考虑每一位,若l[i]这一位是0,那无论x是多少,异或b[i]无论是0,是1,在这一位上都无法作到<l[i],但若l[i]这一位是1,那么x异或b[i]若是0,他们及其之前的数就一定小于l[i],那么一定不合法。同理,对于r[i]若这一位是1我们怎么样都没法在这一位上大于r[i],但若r[i]这一位是0,那若x^b[i]后是1的及其之后的数都一定大于r[i],我们标记不合法,发现这里的标记是区间赋值,我们直接用map做差分即可。

//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d
",x)
#define putl(x) printf("%lld
",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
int l[N],r[N],link[N],tot,b[N],n;
struct bian{int y,next,v;}a[N<<2]; 
map<int,int>mp;
 
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
 
inline void add(int x,int y,int v)
{
    a[++tot].y=y;a[tot].v=v;a[tot].next=link[x];link[x]=tot; 
} 

inline void dfs(int x,int fa)
{
    go(x)
    {
        if(y==fa) continue;
        b[y]=b[x]^a[i].v;
        dfs(y,x);
    }
}

inline void alter(int x,int y)
{
    if(x>r[1]||y<l[1]) return;//将所有不合法的区间去掉,保证所有的区间都在l[1]到r[1]之间 
    x=max(x,l[1]);y=min(y,r[1]);
    mp[x]++;mp[y+1]--;
}

inline void solve()
{
    int ans=0,now=l[1],t=0;
    mp[r[1]+1]++;
    for(auto &p:mp)
    {
        if(t==0) ans+=p.first-now;
        t+=p.second;now=p.first;
    }
    put(ans);
}

int main()
{
//    freopen("1.in","r",stdin);
    get(n);
    rep(i,1,n) get(l[i]),get(r[i]);
    rep(i,1,n-1)
    {
        int get(x),get(y),get(v);
        add(x,y,v);add(y,x,v);
    }
    dfs(1,0);
    rep(i,2,n)
    {
        int x1=0,x2=0;//x1^b[i]和x2^b[i]分别是l[i]和r[i]的前缀.
        fep(j,30,0) 
        {
            int s1=(l[i]&1<<j)?1:0;
            int s2=(r[i]&1<<j)?1:0;
            int s3=(b[i]&1<<j)?1:0;
            if(s1) //若当前l[i]为1,则b[i]^x1为0的位数不合法。 
            {
                int p=x1+((s3^0)<<j);
                alter(p,p+(1<<j)-1);
            }
            if(!s2)//若当前r[i]为0,则b[i]^x2为1的位数不合法。 
            {
                int p=x2+((s3^1)<<j);
                alter(p,p+(1<<j)-1); 
            }
            x1+=(s1^s3)<<j;x2+=(s2^s3)<<j;//更新x1,x2. 
        }
    }
    solve();
    return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
View Code