2019牛客多校第四场

A.meeting

场上写了一坨树形dp。。。
dfs序枚举集合点,答案由三部分组成:
A:从父亲往外的节点(多走一步)得到(一边dfs一边更新)
B:从自己的兄弟(多走两步(先到父亲再往下))得到
C:从自己的子树得到
写得极其凌乱。。打了无数的补丁。。
当然最快的做法就是找到最远关键点对,类似于树的直径跑两遍dfs或dfs即可

//树形dp
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 100005
using namespace std;
priority_queue<int> son[N];
vector<int> e[N];
int b[N],fa[N],dis[N],f[N],pt[N];
int res,n,m,i,x,y;
void dfs(int x)
{
    int i,nxt,delta;
    for (i = 0;i < e[x].size(); i++)
    {
        nxt = e[x][i];
        if (b[nxt] == 1) continue;
        b[nxt] = 1;
        fa[nxt] = x;
        dfs(nxt);
        if (pt[nxt] == 1) dis[x] = max(dis[x],1);
        if (dis[nxt] > 0 || pt[nxt] == 1) delta = 1; else delta = 0;
        dis[x] = max(dis[x],dis[nxt]+delta);
        if (dis[nxt] > 0 || pt[nxt] == 1)
            son[x].push(dis[nxt]);
    }
}
void dfs2(int x)
{
    int i,nxt,A,B,C,temp;
    if (dis[x] > 0 || pt[x] == 1)
    {
        A = f[fa[x]];
        if (A > 0 || pt[fa[x]] == 1) A++;
        if (son[fa[x]].size() <= 1) B = 0; else
        {
            B = son[fa[x]].top();
            if (dis[x] == B/* && son[fa[x]].size() >= 2*/)
            {
                temp = son[fa[x]].top();son[fa[x]].pop();
                B = son[fa[x]].top();
                son[fa[x]].push(temp);
            }
            B += 2;
        }
        C = dis[x];
        res = min(res,max(A,max(B,C)));
        f[x] = max(A,B);
    }
     
    for (i = 0;i < e[x].size(); i++)
    {
        nxt = e[x][i];
        if (b[nxt] == 1) continue;
        b[nxt] = 1;
        dfs2(nxt);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    fo(i,1,n-1)
    {
        scanf("%d%d",&x,&y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    res = 1e9;
    fo(i,1,m) {scanf("%d",&x); pt[x] = 1;}
    fo(i,1,n) b[i] = 0; b[1] = 1;
    dfs(1);
    fo(i,1,n) b[i] = 0; b[1] = 1;
    dfs2(1);
    cout<<res<<endl;
    return 0;
}
//树的直径
#include<iostream>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
using namespace std;
int f[N],a[N],b[N];
int len,pt,n,m,i,x,y;
vector<int> e[N];
void dfs(int x,int d)
{
    int i;
    if (f[x] && d > len)
    {
        len = d;
        pt = x;
    }
    for (i = 0;i < e[x].size(); i++)
        if (b[e[x][i]] == 0)
        {
            b[e[x][i]] = 1;
            dfs(e[x][i],d+1);
        }
}
int main()
{
    scanf("%d%d",&n,&m);
    fo(i,1,n-1)
    {
        scanf("%d%d",&x,&y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    fo(i,1,m) scanf("%d",&a[i]);
    fo(i,1,m) f[a[i]] = 1;
    fo(i,1,n) b[i] = 0; b[1] = 1; len = 0;
    dfs(1,0);
    fo(i,1,n) b[i] = 0; b[pt] = 1; len = 0;
    dfs(pt,0);
    cout<<len/2+(len%2)<<endl;
    return 0;
}

B.xor

核心问题就是线性基求交
假设有两个线性基 A , B A,B A,B,设一个新的线性基 X X X,一开始 X = A X=A X=A,然后不断插入 B B B的元素。对于 B i B_i Bi,如果它能被 X X X线性表示,那么将“ A A A中用来组成 B i B_i Bi的元素”加入答案线性基,否则将这个线性基加入 X X X。(也就是说 X X X中的元素不但要记录值,还要记录由 A A A贡献的部分)
然后。。没了
线段树维护区间线性基
注意单次线性基求交的时间复杂度是 O ( 3 2 2 ) ≈ O ( l o g 2 N ) O(32^2) \approx O(log^2N) O(322)O(log2N)
线段树预处理需要求 O ( N ) O(N) O(N)次交,没有问题
但是线段树合并的时候,如果合并 O ( l o g N ) O(logN) O(logN)个区间,时间复杂度是3个log
实际上我们可以直接询问这 O ( l o g N ) O(logN) O(logN)个区间能否表示出 x x x
单次线性基查询的时间复杂度是 O ( 32 ) O(32) O(32)
总时间复杂度是两个log

#include<iostream>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 500005
#define ll long long
using namespace std;
ll basis[N*4][32];
int n,m,l,r;
ll x;
void insert(int rt,ll x)
{
    int i;
    fd(i,31,0)
    if (x & (1<<i))
        if (basis[rt][i] == 0)
        {
            basis[rt][i] = x;
            return;
        }   else x = x ^ basis[rt][i];
}
void merge(int a,int b,int c)
{
    int i,j; ll u,k;
    ll x[100],y[100];
    fo(i,0,31) x[i] = y[i] = basis[a][i];
    fo(i,0,31)
    if (basis[b][i])
    {
        u = basis[b][i]; k = 0;
        fd(j,31,0)
        if (u & (1<<j))
            if (x[j])
            {
                u = u ^ x[j];
                k = k ^ y[j];
            }   else
            {
                x[j] = u;
                y[j] = k;
                break;
            }
        if (u == 0) insert(c,k);
    }
}
void build(int rt,int l,int r)
{
    int sz; ll x;
    if (l == r)
    {
        scanf("%d",&sz);
        while (sz--) {scanf("%lld",&x); insert(rt,x);}
        return;
    }
    int mid = (l + r) >> 1;
    build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
    merge(rt<<1,rt<<1|1,rt);
}
bool check(int rt,ll x)
{
    int i;
    fd(i,31,0)
    if (x & (1 << i))
        if (basis[rt][i])
            x = x ^ basis[rt][i];
        else return false;
    return true;
}
bool query(int rt,int l,int r,int L,int R,ll x)
{
    if (L <= l && r <= R) return check(rt,x);
    int mid = (l + r) >> 1;
    bool res = true;
    if (L <= mid) res = res && query(rt<<1,l,mid,L,R,x);
    if (mid+1 <= R) res = res && query(rt<<1|1,mid+1,r,L,R,x);
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    build(1,1,n);
    while (m--)
    {
        scanf("%d%d%lld",&l,&r,&x);
        if (query(1,1,n,l,r,x)) printf("YES\n"); else printf("NO\n");
    }
}

C.sequence

我这题用了一个非常慢的两个log的写法。。。。。。
第一个log是查询最小值的位置,然后查询左右子区间的前缀和最大/小值
第二个log是递归的log
ST表竟然卡内存
最后用读优才卡过去
std竟然是 O ( n ) O(n) O(n)
实际上可以枚举每个数作为最小值,用单调栈找到左右区间范围
然后用笛卡尔树直接找到区间最值

#include <bits/stdc++.h>
using namespace std;
namespace fastIO
{
    #define BUF_SIZE 100000
    bool IOerror=0;
    inline char nc()
    {
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
        if (p1==pend)
        {
            p1=buf;
            pend=buf+fread(buf,1,BUF_SIZE,stdin);
            if (pend==p1)
            {
                IOerror=1;
                return -1;
            }
        }
        return *p1++;
    }
    inline bool blank(char ch)
    {
        return ch==' ' || ch=='\n' || ch=='\r' || ch=='\t';
    }
    inline void read(int &x)
    {
        char ch; int f=1;
        while (blank(ch=nc()));
        if (IOerror) return;
        if (ch=='-') {ch=nc(); f = -1;}
        for (x=(ch-'0')*f;(ch=nc())>='0' && ch<='9'; x=x*10+(ch-'0')*f);
    }
    #undef BUF_SIZE
}
using namespace fastIO;
typedef long long ll;
const int maxn=3e6+5;
ll pre[maxn];
int a[maxn],b[maxn],pos[maxn*4];
ll mx[maxn*4],mn[maxn*4];
int c[maxn*4];
int bit[maxn];
int n;
void build2(int rt,int l,int r)
{
    if (l == r) {c[rt] = a[l]; pos[rt] = l; return;}
    int mid = (l + r) >> 1;
    build2(rt<<1,l,mid); build2(rt<<1|1,mid+1,r);
    if (c[rt<<1] < c[rt<<1|1])
    {
        c[rt] = c[rt<<1];
        pos[rt] = pos[rt<<1];
    }   else
    {
        c[rt] = c[rt<<1|1];
        pos[rt] = pos[rt<<1|1];
    }
}
int Qmin(int rt,int l,int r,int L,int R)
{
    if (L <= l && r <= R) return pos[rt];
    int mid = (l + r) >> 1;
    int x,y; x=y=0;
    if (L <= mid) x=Qmin(rt<<1,l,mid,L,R);
    if (mid+1 <= R) y=Qmin(rt<<1|1,mid+1,r,L,R);
    if (x == 0) return y; else if (y == 0) return x; else
    if (a[x] < a[y]) return x; else return y;
}
void build(int rt,int l,int r)
{
    if (l == r) {mx[rt] = mn[rt] = pre[l]; return;}
    int mid = (l + r) >> 1;
    build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
    mx[rt] = max(mx[rt<<1],mx[rt<<1|1]);
    mn[rt] = min(mn[rt<<1],mn[rt<<1|1]);
}
ll qmax(int rt,int l,int r,int L,int R)
{
    if (L <= l && r <= R) return mx[rt];
    int mid = (l + r) >> 1;
    ll res = -1e18;
    if (L <= mid) res = max(res,qmax(rt<<1,l,mid,L,R));
    if (mid+1 <= R) res = max(res,qmax(rt<<1|1,mid+1,r,L,R));
    return res;
}
ll qmin(int rt,int l,int r,int L,int R)
{
    if (L <= l && r <= R) return mn[rt];
    int mid = (l + r) >> 1;
    ll res = 1e18;
    if (L <= mid) res = min(res,qmin(rt<<1,l,mid,L,R));
    if (mid+1 <= R) res = min(res,qmin(rt<<1|1,mid+1,r,L,R));
    return res;
}
const int delta=1e6;
ll ans=-1e18;
void solve(int l,int r)
{
    if (l>r) return;
    int p=Qmin(1,1,n,l,r);
    int k = a[p];
    if (k>0)
    {
        ll rp=qmax(1,1,n,p,r);
        ll lp;
        if (p==1) lp=0;
        else lp=qmin(1,1,n,max(l-1,1),p-1);
        ans=max(ans,1ll*k*(rp-lp));
    }
    else if (k<0)
    {
        ll rp=qmin(1,1,n,p,r);
        ll lp;
        if (p==1) lp=0;
        else lp=qmax(1,1,n,max(l-1,1),p-1);
        ans=max(ans,1ll*k*(rp-lp));
    }
    else ans=max(ans,0ll);
    solve(l,p-1);
    solve(p+1,r);
}
int main()
{
    //freopen("1.in","r",stdin);
    read(n);
    for (int i=1;i<=n;i++)
        read(a[i]);
    for (int i=1;i<=n;i++)
        read(b[i]);
    build2(1,1,n);
    //init(n,a);
    for (int i=1;i<=n;i++)
        pre[i]=pre[i-1]+b[i];
    build(1,1,n);
    solve(1,n);
    printf("%lld\n",ans);
    return 0;
}

D.triples I

考虑奇数位上的1和偶数位上的1
奇数位模3余2,偶数位模3余1
考虑若干种情况…
1:只有奇数位或者偶数位。实际上是类似区间交的情况,假设有17个,那么可以把前15个数or起来作为ans1,多的2个数从前面拉过来一个数凑成三个数,or起来作为ans2
2:同时有奇数位和偶数位。先两两配对作为ans1,然后把多的那部分全部or起来。假设多的是奇数位,如果多的个数是3的倍数,那么刚好;如果多一个,那么再找一个配对过的偶数位;如果多两个,那么再找一个配对过的奇数位。

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
long long T,x,op,bit,even,odd,eveneven,oddodd,res1,res2,delta,i;
long long a[100],b[100];
int main()
{
    scanf("%d",&T);
    while (T--)
    {
        odd = even = 0;
        scanf("%lld",&x);
        op = 1;//odd=1
        bit = 0;
        while (x)
        {
            if (x&1)
            {
                if (bit % 2 == 0)
                {
                    even++;
                    a[even] = 1ll << bit;
                }   else
                {
                    odd++;
                    b[odd] = 1ll << bit;
                }
            }
            bit++; x /= 2;
        }
        oddodd = odd;
        eveneven = even;
        if (odd == 0)
        {
            res1 = res2 = 0;
            delta = even % 3;
            fo(i,1,even-delta) res1 = res1 | a[i];
            if (delta)
            fo(i,even-2,even) res2 = res2 | a[i];
            if (res2 == 0) cout<<1<<" "<<res1<<endl;
            else if (res1 == 0) cout<<1<<" "<<res2<<endl;
                else cout<<2<<" "<<res1<<" "<<res2<<endl;
            //cout<<(res1|res2)<<endl;
            continue;
        }
        if (even == 0)
        {
            res1 = res2 = 0;
            delta = odd % 3;
            fo(i,1,odd-delta) res1 = res1 | b[i];
            if (delta)
            fo(i,odd-2,odd) res2 = res2 | b[i];
            if (res2 == 0) cout<<1<<" "<<res1<<endl;
            else if (res1 == 0) cout<<1<<" "<<res2<<endl;
                else cout<<2<<" "<<res1<<" "<<res2<<endl;
            //cout<<(res1|res2)<<endl;
            continue;
        }
        res1 = 0;
        while (odd > 0 && even > 0)
        {
            res1 = res1 | a[even];
            res1 = res1 | b[odd];
            even--;
            odd--;
        }
        res2 = 0;
        if (odd > 0)
        {
            delta = odd % 3;
            while (odd) {res2 = res2 | b[odd]; odd--;}
            if (delta == 1) res2 = res2 | a[1];
            if (delta == 2) res2 = res2 | b[oddodd];
        }
        if (even > 0)
        {
            delta = even % 3;
            while (even) {res2 = res2 | a[even]; even--;}
            if (delta == 1) res2 = res2 | b[1];
            if (delta == 2) res2 = res2 | a[eveneven];
        }
        if (res2 == 0) cout<<1<<" "<<res1<<endl;
            else if (res1 == 0) cout<<1<<" "<<res2<<endl;
                else cout<<2<<" "<<res1<<" "<<res2<<endl;
        //cout<<(res1|res2)<<endl;
    }
    return 0;
}

E.triples II

warning:以下题解的奇偶性如果和程序不符不要怪我。。。
f [ i ] [ j ] f[i][j] f[i][j]表示最多用i个奇数位和j个偶数位能够凑出的3的倍数的个数
这里是“最多”,所以要去掉严格小于的情况
这其实是一个容斥原理的模型
比如要求x个奇数位和y个偶数位
不严格的讲就是:
首先答案是 f [ x ] [ y ] f[x][y] f[x][y],然后去掉非法的 f [ x − 1 ] [ y ] f[x-1][y] f[x1][y] f [ x ] [ y − 1 ] f[x][y-1] f[x][y1],然后还得加上多算的 f [ x ] [ y ] f[x][y] f[x][y]…然后算每一部分都要继续容斥下去
因此答案:
a n s = ∑ 0 ≤ i ≤ x , 0 ≤ j ≤ y p o w ( f [ i ] [ j ] , n ) C x i C y j s i g ( i , j ) ans=\sum _{ 0\le i\le x,0\le j\le y }^{ }{ pow(f[i][j],n){ C }_{ x }^{ i }{ C }_{ y }^{ j }sig(i,j) } ans=0ix,0jypow(f[i][j],n)CxiCyjsig(i,j)
其中,若 i + j i+j i+j的奇偶性和 x + y x+y x+y相同则 s i g = 1 sig=1 sig=1,否则等于 − 1 -1 1

#include<iostream>
#include<vector>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
#define ll long long
#define MOD 998244353
using namespace std;
ll c[100][100],f[100][100];
int T,op,x,y,i,j;
ll n,a,res,res_t;
void init_c()
{
    int i,j;
    fo(i,0,64)
    {
        c[i][0] = 1;
        fo(j,1,i) c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;
    }
}
void init()
{
    int  i,j,i1,j1;
    fo(i,0,64)
    fo(j,0,64)
    {
        fo(i1,0,i)
        fo(j1,0,j)
            if ((i1 + j1 * 2) % 3 == 0)
                f[i][j] = (f[i][j] + (c[i][i1] * c[j][j1]) % MOD) % MOD;
    }
}
ll qp(ll a,ll x)
{
    ll res = 1; ll e = a;
    while (x)
    {
        if (x&1) res = (res * e) % MOD;
        e = (e * e) % MOD;
        x >>= 1;
    }
    return res;
}
int main()
{
    init_c();
    init();
    scanf("%d",&T);
    while (T--)
    {
        scanf("%lld%lld",&n,&a);
        op = x = y = 0;
        while (a)
        {
            if (a&1) if (op==0) x++; else y++;
            a >>= 1;
            op = 1 - op;
        }
        res = 0;
        fo(i,0,x)
        fo(j,0,y)
        {
            res_t = 1;
            res_t = (c[x][i] * c[y][j]) % MOD;
            res_t = (res_t * qp(f[i][j],n)) % MOD;
            if ((i+j) % 2 == (x+y) % 2) res_t *= 1; else res_t *= -1;
            res = (res + res_t + MOD) % MOD;
        }
        cout<<res<<endl;
    }
    return 0;
}

J.free

f [ i ] [ j ] f[i][j] f[i][j]表示走到 i i i这个点,并且最多有 j j j条边免费的最短路
那么 f [ i ] [ j ] = m i n ( f [ i ] [ j − 1 ] , m i n ( f [ x ] [ j − 1 ] , f [ x ] [ j ] + e ) ) f[i][j]=min(f[i][j-1],min(f[x][j-1],f[x][j]+e)) f[i][j]=min(f[i][j1],min(f[x][j1],f[x][j]+e)),其中 e e e表示 x x x走到 i i i距离
按照 j j j从小到大转移
每次初始化 f [ i ] [ j ] = m i n ( f [ i ] [ j − 1 ] , f [ x ] [ j − 1 ] ) f[i][j]=min(f[i][j-1],f[x][j-1]) f[i][j]=min(f[i][j1],f[x][j1]),然后跑最短路

K.number

f [ i ] [ 0 / 1 / 2 ] f[i][0/1/2] f[i][0/1/2]表示第 i i i个数必取,前面组成的所有数模3余0/1/2的个数
如果 s [ i + 1 ] = s [ i + 2 ] = 0 s[i+1]=s[i+2]=0 s[i+1]=s[i+2]=0,那么把 f [ i ] [ 0 ] f[i][0] f[i][0]加入答案
最后加入 ′ 0 ′ &#x27;0&#x27; 0 ′ 0 0 ′ &#x27;00&#x27; 00的情况

#include<bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 200000
using namespace std;
long long n,i,j,t;
long long res;
long long a[N],f[N][5];
char ch[N];
int main()
{
    scanf("%s",ch+1);
    n = strlen(ch+1);
    fo(i,1,n) a[i] = ch[i] - '0';
    fo(i,1,n) if (a[i] == 0) res++;
    fo(i,1,n-1) if (a[i] == 0 && a[i+1] == 0) res++;
    fo(i,1,n)
    {
        fo(j,0,2)
        {
            t = (a[i] + j) % 3;
            f[i][t] += f[i-1][j];
        }
        f[i][a[i]%3] += 1;
    }
    fo(i,1,n-1)
        if (a[i] == 0 && a[i+1] == 0)
            res += f[i-1][0];
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值