2019牛客暑期多校训练营(第四场)

A - meeting

题意:

在一棵树上,多个节点有人。选择一个节点使得这些人到这个点的路径最大值最小。

解析:

将不存在人的叶子都割掉,直到所有叶子都是人为止,你会发现就是求剩下来树的直径。

代码:

#include
using namespace std;
const int N=1e5+5;
struct node
{
    int to,next;
}e[N*2];
int cnt,head[N];
void add(int x,int y)
{
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
int is[N],mx,p,ans;
void dfs(int x,int fa,int dis)
{
    if(dis>mx&&is[x])
        mx=dis,p=x;
    for(int i=head[x];~i;i=e[i].next)
    {
        int ne=e[i].to;
        if(ne==fa)
            continue;
        dfs(ne,x,dis+1);
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    int n,k,x,y;
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;i++)
        scanf("%d%d",&x,&y),add(x,y),add(y,x);
    for(int i=1;i<=k;i++)
        scanf("%d",&x),is[x]=1;
    p=x,mx=0;
    dfs(x,0,0);
    dfs(p,0,0);
    printf("%d\n",(mx+1)/2);
}

B - xor

题意:

给出n个集合,m个查询,每个查询 ( L , R , v a l ) (L,R,val) (L,R,val),询问区间 [ L , R ] [L,R] [L,R]中的所有集合是否可以通过集合内异或来表示 v a l val val

解析:

异或表示显然就是线性基了,一个线性基是否可以表示一个数大家都会做,现在要求区间内所有线性基是否可以。

先来说一个很显然的东西,我们定义两个线性基的交为:两个基都可以表示的位。

我在之前讲过,线性基里面的每个基相当于二进制中的一个比特位,假设我第一个线性基可以表示 30 , 28 , 26 , 20 30,28,26,20 30,28,26,20,第二个 29 , 28 , 20 , 19 29,28,20,19 29,28,20,19,那么两个线性基的交就是 28 , 20 28,20 28,20

那么这道题显然考察的就是线性基的交,我们只需要求出一个区间内所有线性基的交,再用这个线性基去判断即可。如果交可以表示,那么显然区间内的所有线性基都可以表示了。

这个过程可以用线段树去维护,每个节点存对应区间的所有线性基的交。

代码:

#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
struct linear_Bace;
typedef linear_Bace LB;
const int maxn=50000+9;
const int Len=33;

struct linear_Bace{
    int a[Len];
    int& operator [](int idx){
        return a[idx];
    }
    int operator [](int idx)const{
        return a[idx];
    }
    void insert(int val){
        for(int i=Len-1;i>=0;i--){
            if((val>>i)&1){
                if(!a[i]){
                    a[i]=val;break;
                }
                val^=a[i];
            }
        }
    }
    bool find(int val){
        for(int i=Len-1;i>=0;i--){
            if((val>>i)&1){
                if(a[i]){
                    val^=a[i];
                }
                else
                    return 0;
            }
        }
        return 1;
    }
};
LB merge(LB a,LB b){
    LB ans=a;
    for(int i=Len-1;i>=0;i--){
        if(b[i]==0)continue;
        ans.insert(b[i]);
    }
    return ans;
}
LB intersect(LB a,LB b){
    LB ans= {},c=b,d=b;
    for(int i=0;i<Len;i++) {
        int x=a[i];
        if(!x)
            continue;
        int j=i;
        int T=0;
        for(; j>=0; --j){
            if((x>>j)&1)
                if(c[j]) {
                    x^=c[j];
                    T^=d[j];
                } else
                    break;
        }

        if(!x)
            ans[i]=T;
        else {
            c[j]=x;
            d[j]=T;
        }
    }
    return ans;
}

LB arr[maxn];
LB tr[maxn<<2];
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)

void build(int rt,int l,int r){
    if(l==r){
        tr[rt]=arr[l];
        return ;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    tr[rt]=intersect(tr[ls],tr[rs]);
}

bool query(int rt,int l,int r,int L,int R,int val){
    if(l>=L&&r<=R){
        return tr[rt].find(val);
    }
    if(L<=mid)
        if(!query(ls,l,mid,L,R,val))return 0;
    if(R>mid)
        if(!query(rs,mid+1,r,L,R,val))return 0;
    return 1;
}

int main(){
    int n,q;scanf("%d%d",&n,&q);
    rep(i,1,n){
        int k;scanf("%d",&k);
        while(k--){
            int val;scanf("%d",&val);
            arr[i].insert(val);
        }
    }
    build(1,1,n);
    while(q--){
        int l,r,val;scanf("%d%d%d",&l,&r,&val);
        printf("%s\n",query(1,1,n,l,r,val)?"YES":"NO");
    }
}

C - sequence

题意:

给出两个数组 a , b a,b a,b,区间 [ L , R ] [L,R] [L,R]值为 m i n ( a [ L , R ] ) ∗ s u m ( b [ L , R ] ) min(a_{[L,R]})*sum(b_{[L,R]}) min(a[L,R])sum(b[L,R]),求值的最大值。

解析:

枚举每个数作为最小值的区间,如果 a i a_i ai为为正数,区间和最大的为前缀和数组在右边的最大值减左边的最小值, a i a_i ai为负数时相反。

维护每个数的区间可以用单调栈或者笛卡尔树做。

前缀和区间最值用线段树维护。

代码:

#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define LL long long
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+r>>1)
const int maxn=3e6+5;
 
LL a[maxn];
LL b[maxn];
LL pre[maxn];
int _rt,_ls[maxn],_rs[maxn];
int top;
int sta[maxn];
bool vis[maxn];
LL ma[maxn<<2];
LL mi[maxn<<2];
 
void build(int rt,int l,int r){
    if(l==r){
        ma[rt]=mi[rt]=pre[l];
        return;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    ma[rt]=max(ma[ls],ma[rs]);
    mi[rt]=min(mi[ls],mi[rs]);
}
LL query_max(int rt,int l,int r,int L,int R){
    if(l>=L&&r<=R){
        return ma[rt];
    }
    LL ans=-1e18;
    if(L<=mid)ans=query_max(ls,l,mid,L,R);
    if(R>mid)ans=max(ans,query_max(rs,mid+1,r,L,R));
    return ans;
}
LL query_min(int rt,int l,int r,int L,int R){
    if(l>=L&&r<=R){
        return mi[rt];
    }
    LL ans=1e18;
    if(L<=mid)ans=query_min(ls,l,mid,L,R);
    if(R>mid)ans=min(ans,query_min(rs,mid+1,r,L,R));
    return ans;
}
 
void init(int n){
    int tmp;
    top=0;
    rep(i,1,n){
        vis[i]=0;
        _ls[i]=_rs[i]=0;
    }
    rep(i,1,n){
        tmp=top;
        while(tmp&&a[sta[tmp-1]]>a[i])tmp--;
        if(tmp)_rs[sta[tmp-1]]=i;
        if(top>tmp)_ls[i]=sta[tmp];
        sta[tmp++]=i;
        top=tmp;
    }
    rep(i,1,n){
        vis[_ls[i]]=vis[_rs[i]]=1;
    }
    rep(i,1,n){
        if(!vis[i]){
            _rt=i;break;
        }
    }
}
 
int n;
void dfs(int _rt,int l,int r,LL &ans){
    if(l>=r)return;
    if(a[_rt]==0){
        ans=max(ans,0ll);
    }
    else if(a[_rt]>0){
        LL Ma=query_max(1,1,n,_rt,r);
        LL Mi;
        if(_rt==1)Mi=0;
        else if(l==1)Mi=min(0ll,query_min(1,1,n,1,_rt-1));
        else Mi=query_min(1,1,n,l-1,_rt-1);
        ans=max(ans,a[_rt]*(Ma-Mi));
    }
    else{
        LL Mi=query_min(1,1,n,_rt,r);
        LL Ma;
        if(_rt==1)Ma=0;
        else if(l==1)Ma=max(0ll,query_max(1,1,n,1,_rt-1));
        else Ma=query_max(1,1,n,l-1,_rt-1);
        ans=max(ans,a[_rt]*(Mi-Ma));
    }
 
 
    dfs(_ls[_rt],l,_rt-1,ans);
    dfs(_rs[_rt],_rt+1,r,ans);
}
 
int main(){
    scanf("%d",&n);
    LL ans=-1e18;
    rep(i,1,n){
        scanf("%lld",&a[i]);
    }
 
    rep(i,1,n){
        scanf("%lld",&b[i]);
        ans=max(ans,a[i]*b[i]);
        pre[i]=pre[i-1]+b[i];
    }
 
    init(n);
    build(1,1,n);
 
    dfs(_rt,1,n,ans);
    printf("%lld\n",ans);
}

D - triples I

题意:

找多个3的倍数,使其位或后等于给出的数。要求个数最少。

解析:

看成异或做了好久。。。按二进制看,奇数位模3余1,偶数位模3余2,也就是说把二进制看成 1212121 1212121 1212121,自己选择方案使得选择的数所有位之和为3的倍数即可。

代码:

#include
using namespace std;
typedef long long ll;
vector<int> bla1,bla2;
vector<int> hav1,hav2;
int bit[70],ct;
ll n;
void deal(ll x){
    ct=0;
    ll tmp=x;
    memset(bit,0,sizeof(bit));
    while(tmp){
        bit[ct]=tmp%2;
        ct++;
        tmp/=2;
    }
}
void add(ll pos,int flag,ll &ans){
    if(flag==1){
        pos=hav1[pos];
        ans+=(1ll<<pos);
 
    }
    else{
        pos=hav2[pos];
        ans+=(1ll<<pos);
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%lld",&n);
        if(n%3==0){
            printf("1 %lld\n",n);
            continue;
        }
        hav1.clear(); hav2.clear();
        deal(n);
        for(int i=0;i<60;i++){
            if(bit[i]){
                if(i%2) hav2.push_back(i);
                else hav1.push_back(i);
            }
        }
        int h1=hav1.size(),h2=hav2.size();
        int cha=max(h1,h2)-min(h1,h2),now=min(h1,h2);
        int addh1=now,addh2=now;
        if(h1>h2) addh1+=cha-cha%3;
        else addh2+=cha-cha%3;
        ll ans1=0,ans2=0;
        ll p1,p2;
        cha=cha%3;
        if(h1>h2&&cha==1||h2>h1&&cha==2){
            if(cha==1){
                add(addh1,1,ans2);
                if(hav2.size()) add(0,2,ans2);
                else add(addh1-1,1,ans2),add(addh1-2,1,ans2);
            }
            else{
                add(addh2,2,ans2); add(0,2,ans2);
                add(addh2+1,2,ans2);
            }
            for(int i=0;i<addh1;i++){
                add(i,1,ans1);
            }
            for(int i=0;i<addh2;i++){
                add(i,2,ans1);
            }
        }
        else{
            if(cha==2){//差两个1
                add(addh1,1,ans2);
                add(addh1+1,1,ans2); add(0,1,ans2);
            }
            else{//差一个2
                add(addh2,2,ans2);
                if(hav1.size()) add(0,1,ans2);
                else add(addh2-1,2,ans2),add(addh2-2,2,ans2);
            }
            for(int i=0;i<addh1;i++){
                add(i,1,ans1);
            }
            for(int i=0;i<addh2;i++){
                add(i,2,ans1);
            }
        }
        printf("2 %lld %lld\n",ans1,ans2);
 
    }
    return 0;
}

I - string

题意:

给出一个串,找出一个最大的子串集合,使得集合内任意两个串不相同(翻转后也算相同: a b c = c b a abc=cba abc=cba

解析:

尝试用后缀自动机来做(可以得出出现次数为 k k k的子串的数量)。观察一个串与它的翻转: a b a c , c a b a abac,caba abac,caba


分析情况:

F ( s ) F(s) F(s)为子串 s s s在原串以及其翻转中出现的次数之和。

  • 假设 F ( a b ) = F ( b a ) = 1 F(ab)=F(ba)=1 F(ab)=F(ba)=1,说明 a b ab ab b a ba ba有且只有一个在原串中出现恰好一次。(很显然)
    此时正确答案为1,但在后缀自动机中统计的为2。
  • 假设 F ( s ) > 1 F(s)>1 F(s)>1,则说明 s s s s − 1 s^{-1} s1在原串中出现的次数之和大于1。分下面两种情况:
    1. s s s为回文串时,此时答案为1,我们统计的也是1,没有问题;
    2. s s s不是回文串时,此时答案也为1,但是我们统计的是2。

综上所述,只有回文串的情况下不是2倍。


两个串怎么一起跑后缀自动机?

我们可以构建一个新串: S + ′ ∗ ′ + S − 1 S+'*'+S^{-1} S++S1,这样,新多出的子串必须中间符号,我们可以直接算出这部分子串的数量为 ( L e n S + 1 ) 2 (Len_S+1)^2 (LenS+1)2


回文串怎么处理?

显然,我们可以先加上 s s s为回文串的情况数,再除二进行计算。那么这个情况数是什么呢?

分析后得出为原串中本质不同的回文串个数。这个东西直接用回文自动机跑出来就行。


计算结果:

我们让后缀自动机跑出出现1次以上的子串数量 a n s ans ans,也就是本质不同的子串的数量。

此时会计入包含 ′ ∗ ′ '*' 的串,所以 a n s = a n s − ( L e n S + 1 ) 2 ans=ans-(Len_S+1)^2 ans=ans(LenS+1)2

然后我们加上本质不同的回文串的数量后再除以二就是答案。


代码:

#include
#define ll long long
#define PB push_back
#define fst first
#define sec second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define ms(a,x) memset(a,x,sizeof(a))
typedef long long LL;
#define pi pair < int ,int >
#define MP make_pair
 
using namespace std;
const double eps = 1E-8;
const int dx4[4]={1,0,0,-1};
const int dy4[4]={0,-1,1,0};
const int inf = 0x3f3f3f3f;
const int N = 4e5+1000;
char s[N];
int k,lens;
struct PAM
{
    int fail,cnt,len;
    int nxt[27];
}st[N];
char RS[N];
int n,now,sz;
void pam_init()
{
    ms(st,0);
    st[0].fail = st[1].fail = 1;
    st[1].len = -1;
    sz = 1;
}
void extend(int c,int pos)
{
    int p = now;
    while (s[pos-st[p].len-1]!=s[pos]) p = st[p].fail;
    if (!st[p].nxt[c]){
    int np=++sz,q=st[p].fail;
    st[np].len=st[p].len+2;
    while (s[pos-st[q].len-1]!=s[pos]) q=st[q].fail;
    st[np].fail=st[q].nxt[c];
    st[p].nxt[c] = np;
    }
    now=st[p].nxt[c];
    st[now].cnt++;
}
struct SAM{
    int last,cnt,nxt[N*2][27],fa[N*2];
    ll l[N*2],num[N*2];
    ll ans;
    void init(){
        last = cnt=1;
        memset(nxt[1],0,sizeof nxt[1]);
        fa[1]=l[1]=num[1]=0;
        ans=0;
    }
    int inline newnode(){
        cnt++;
        memset(nxt[cnt],0,sizeof nxt[cnt]);
        fa[cnt]=l[cnt]=num[cnt]=0;
        return cnt;
    }
    void add(int c){
        int p = last;
        int np = newnode();
        last = np;
        l[np] =l[p]+1;
        while (p&&!nxt[p][c]){
            nxt[p][c] = np;
            p = fa[p];
        }
        if (!p){
            fa[np] =1;
        }else{
            int q = nxt[p][c];
            if (l[q]==l[p]+1){
                fa[np] =q;
            }else{
                int nq = newnode();
                memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
                fa[nq] =fa[q];
                num[nq] = num[q];
                l[nq] = l[p]+1;
                fa[np] =fa[q] =nq;
                while (nxt[p][c]==q){
                    nxt[p][c]=nq;
                    p = fa[p];
                }
            }
        }
        int temp = last;
        while (temp){
            if (num[temp]>=k){
                break;
            }
            num[temp]++;
            if (num[temp]==k){
                ans+=l[temp]-l[fa[temp]];
            }
            temp = fa[temp];
        }
    }
}sam;
char ss[N];
int main()
{
    scanf("%s",s);
    int len=strlen(s);
    pam_init();
    vector <ll>aa;
    for ( int i = 0 ; i < len; i++) extend(s[i]-'a',i),aa.PB(sz-1);
    int siz = aa.size();
    sam.init();
    k=1;
    for(int i=0;i<len;i++)
        sam.add(s[i]-'a');
    sam.add(26);
    for(int i=len-1;i>=0;i--)
        sam.add(s[i]-'a');
    ll ans=sam.ans-(ll)(len+1)*(len+1);
    printf("%lld\n",(ans+aa[siz-1])/2ll);
    return 0;
}

J - free

题意:

给出一个图,你可以让你路径上 k k k条边的权值变为0,求从 s s s t t t的最小权值。

解析:

直接迪杰斯特拉就行。

代码:

#include
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn = 1005;
const int maxm=1005;
const int inf=(int)1e9;
int dp[maxn][maxn];
//走到点i用了j次机会的最短路
int cnt,head[maxn],n,m,S,T,k,now,vis[maxn];
struct node{
    int to,next,w;
}e[maxn*2];
struct  Node{
    int dis,id,ti;
    Node(int dis,int id,int ti):dis(dis),id(id),ti(ti){}
    bool operator < (const Node &a)const{
        if(dis==a.dis) return ti>a.ti;
        return dis>a.dis;
    }
};
void init(){
    memset(head,-1,sizeof(head));
    cnt=0;
}
void add(int u,int v,int w){
    e[cnt].to=v,e[cnt].w=w;
    e[cnt].next=head[u],head[u]=cnt++;
}
void dij(int st){
    for(int i=1;i<=n;i++){
        for(int j=0;j<=k;j++) dp[i][j]=inf;
        vis[i]=0;
    }
    for(int i=0;i<=k;i++) dp[st][i]=0;
    priority_queue<Node> q;
    q.push(Node(0,st,0));
    while(!q.empty()){
        int u=q.top().id; q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(vis[v]) continue;
            for(int j=0;j<=k;j++){
                if(dp[v][j]>dp[u][j]+e[i].w){
                    dp[v][j]=dp[u][j]+e[i].w;
                    q.push(Node(dp[v][j],v,j));
                }
                if(j<k&&dp[v][j+1]>dp[u][j]){
                    dp[v][j+1]=dp[u][j];
                    q.push(Node(dp[v][j+1],v,j+1));
                }
            }
            for(int j=1;j<=k;j++){
                if(dp[v][j+1]>dp[v][j]) dp[v][j+1]=dp[v][j];
            }
        }
    }
}
int main(){
    init();
    scanf("%d%d%d%d%d",&n,&m,&S,&T,&k);
    rep(i,1,m){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);  add(y,x,z);
    }
    dij(S);
    int ans=dp[T][0];
    for(int i=1;i<=k;i++){
        ans=min(ans,dp[T][i]);
    }
    printf("%d\n",ans);
    return 0;
}

K - number

题意:

求可以被300整数的子串数量。

解析:

处理 0 0 0 00 00 00的情况后,相当于找出前面有多少个被3整数的子串。显然状态只有余 0 , 1 , 2 0,1,2 0,1,2三种。

代码:

#include
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define LL long long
int main(){
    string x;cin>>x;
    int ct[3]={0,0,0};
    LL ans=0;
    rep(i,0,x.length()-1){
        if(x[i]=='0'){
            ans++;
            if(i+1<x.length()&&x[i+1]=='0'){
                ans++;
                ans+=(LL)ct[0];
            }
        }
 
        int add=(x[i]-'0')%3;
        int tmp[3];
        rep(j,0,2){
            tmp[(j+add)%3]=ct[j];
        }
        tmp[(x[i]-'0')%3]++;
        rep(i,0,2)ct[i]=tmp[i];
    }
    printf("%lld\n",ans);
}

你可能感兴趣的:(2019牛客暑期多校训练营(第四场))