2021牛客暑期多校訓練營3


比賽鏈接:https://ac.nowcoder.com/acm/contest/11254

很費勁地 3/10 ,但是校內第18名囧。

一開始沒什么一眼做出來的題;后來發現J題好多人過,於是G開始寫J;但似乎寫了一會以后遇到了障礙,停下繼續推了起來。而我先看了B,E,F,發現B題漸漸有一些人過了,於是開始看B。此時Y也在看B。一個小時多一點的時候,J過了;Y在看E,我在看B,然后G看F——目前就是B,E,F,J過的人比較多。B應該是 \( O(n^2) \) 做,就開始想DP,但是沒什么很好的想法。之后Y發現了E題的規律,寫出了個式子,於是我兩人就一起討論怎么寫出來;討論一番,覺得預處理一下,然后二分就能寫。此時G發現F題能搜索,已寫了半天。然后Y去寫E題,很快寫完,但遇到了點問題,G也去調了半天,就過了。這時已經四點了。G繼續寫F題,而Y和我一起看B題;我又寫了DP的轉移方程,但是怎么看也是 \( O(n^3) \) 的,苦惱。Y在考慮構造,但也沒想好。還剩十分鍾的時候F題終於過了。於是我上去寫了個B題的簡單假算法,果然WA了,而且還一個點都沒過,真不給面子啊。

E:Math

題意:

\(t\)組數據,每次給一個\(n\),求滿足\( (xy+1) | (x^2+y^2) \)的\( (x,y) \)個數,\(1 \leq x,y \leq n\)。\(1 \leq t \leq 10^5, 1 \leq n \leq 10^18\)。

分析:

通過打表,發現除了\((1,1)\),滿足條件的是所有\( (k,k^3) \),以及它們遞推下去的數對;

就是說,如果\((a,b)\)滿足條件,而且是從\((k,k^3)\)推出來的,那么\( (b,b*k^2-a) \)也是滿足條件的數對;

所以找到所有滿足條件的數對(也不是很多),每次詢問upper_bound即可。

數字比較大,需要用__int128.

Y寫了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e7+10;
ll num[MAXN];
int T,cnt;
ll ans;
void init(){
    num[1]=1;cnt=1;
    __int128 a,b;
    for(int i=2;i<=1e6;i++){
        a=i;b=i,b=b*i*i;
        while(b<=1e18){
            //if(b==2211||b==2424||b==4863) printf("%lld %lld\n",(ll)a,(ll)b);
            num[++cnt]=(ll)b;
            ll t=b;
            b=b*i*i-a;a=t;
            //printf("i=%d a=%lld b=%lld\n",i,(ll)a,(ll)b);
            //if(b>1e18||b<0) printf("i=%d b=%lld\n",i,(ll)b),exit(0);
        }
    }
    sort(num+1,num+1+cnt);
    cnt=unique(num+1,num+1+cnt)-num-1;
}
int main()
{   //freopen("E.in","w",stdout);
    init();
    //printf("cnt=%d\n",cnt);
    //for(int i=1;i<=40;i++) printf("%d %lld\n",i,(ll)num[i]);
    for(scanf("%d",&T);T;T--){
        ll n;
        scanf("%lld",&n);
        ans=upper_bound(num+1,num+1+cnt,n)-num-1;
        printf("%lld\n",ans);
    }
}
Y

 

G:Yu Ling(Ling YueZheng) and Colorful Tree

題意:

給定一棵\( n \)個點的樹(有邊權),給\( q \)次操作,分為兩種:

\( 0 , u , x \) :把點\( u \)染成顏色\( x \),這里顏色彼此不同而且范圍在\( n \)以內;

\( 1 , u , l , r , x \) :查詢離\( u \)最近的、染過色而且顏色\( c \)滿足\( l \leq c \leq r , c mod x = 0 \)的祖先到\( u \)的距離。

\( 1 \leq n , q \leq 1.1*10^5 \) 。

分析:

看了第一個通過的隊伍的代碼,竟然是樹狀數組套線段樹,復雜度\( O(nlog^2(n)) \),很巧妙。

首先,需要處理\( c \% x = 0 \)這個要求——可以離線做,然后把操作分類,將有倍數關系的操作建立關系;暫時可以這樣想。

查詢要找的是祖先——可以反過來想,染色操作會對被染色點的子樹有影響,而操作子樹貌似比操作祖先方便一些。

綜合起來,我們可以先找到每個顏色的因數,把它們存在vector里;染上某個顏色的操作會影響到該顏色所有的因數,於是把這個操作分別放進它所有因數的存儲操作的另一個vector里;當然,查詢某個顏色的操作也放進那個顏色的操作vector里;這個vector里的操作要按順序放。

於是我們可以考慮每個顏色的操作序列;按順序進行修改和查詢操作。

為了方便地對子樹整體修改,我們求出這棵樹的dfs序,在dfs序列上用樹狀數組進行區間操作,給染色點的子樹進行某些修改。

下面我們考慮\( l \leq c \leq r \)這個要求——如果用線段樹維護顏色值序列,那么這個也可以方便地查詢;

也就是說,當出現一個查詢操作時,我們要在查詢點的線段樹內查找\( l , r \)范圍內是否有數——只要有數即可,因為前面只進行過當前顏色的倍數顏色的染色操作。

現在我們就可以寫出樹狀數組套線段樹了:樹狀數組建在dfs序列上,其中每個點有一個線段樹建在顏色數值上;修改操作就是給修改點的子樹中每個點的線段樹上染色顏色位置\( +1 \),查詢操作就是在查詢點的線段樹上提取\( l , r \)區間的和;

這時查詢操作做了一半——我們可以知道對於當前點,有多少個祖先符合條件;

倍增記錄的祖先節點,我們可以找一下到哪個祖先節點時,它也有同樣個數的祖先符合條件;最淺的那個點就是答案,\( dis[u] \)相減即得距離。

那么空間復雜度怎么辦?實際上,我們可以用到線段樹節點時再把它開出來;而對於每個顏色的操作我們最多用到\( qlog^2(n) \)個線段樹點,所以用時再開,用完刪除即可。

時間復雜度是\( O(nlog^2(n)) \)。

(過程中順便仔細又看了看樹狀數組,這篇博客挺好的。)

代碼如下:

#include<iostream>
#include<vector>
#define mid ((l+r)>>1)
#define pb push_back
#define ll long long
using namespace std;
int const N=110005,M=20000005;///
int n,q,hd[N],nxt[N<<1],cnt,to[N<<1],fa[N][20],dfn[N],ed[N],dcnt;
int rt[N],ls[M],rs[M],tcnt,tr[M];
ll w[N<<1],dis[N],ans[N];
vector<int>ele[N];
struct Nd{
    int u,l,r,id;
};
vector<Nd>v[N];
void init()
{
    for(int x=1;x<=n;x++)
        for(int j=x;j<=n;j+=x)ele[j].pb(x);//j mod x = 0 同余類
}
void add(int x,int y,ll s){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y; w[cnt]=s;}
void dfs(int u)
{
    dfn[u]=++dcnt;
    for(int i=hd[u],v;i;i=nxt[i])
    {
        if((v=to[i])==fa[u][0])continue;
        fa[v][0]=u; dis[v]=dis[u]+w[i];
        //dfs(v);//
        for(int i=1;i<20;i++)
            fa[v][i]=fa[fa[v][i-1]][i-1];
        dfs(v);//注意順序TAT
    }
    ed[u]=dcnt;
}
//線段樹,序列是染色數值
void chg2(int &x,int l,int r,int p,int s)
{
    if(!x)x=++tcnt; tr[x]+=s;
    if(l==r)return;
    if(p<=mid)chg2(ls[x],l,mid,p,s);
    else chg2(rs[x],mid+1,r,p,s);
}
int qry2(int x,int l,int r,int ql,int qr)
{
    if(!x||(l>=ql&&r<=qr))return tr[x];
    int ret=0;
    if(mid>=ql)ret+=qry2(ls[x],l,mid,ql,qr);
    if(mid<qr)ret+=qry2(rs[x],mid+1,r,ql,qr);
    return ret;
}
//樹狀數組,序列是dfs序
void chg1(int x,int p,int s){for(;x<=n;x+=(x&(-x)))chg2(rt[x],1,n,p,s);}
int qry1(int x,int l,int r){int ret=0; for(;x;x-=(x&-x))ret+=qry2(rt[x],1,n,l,r); return ret;}
void dell(int x){for(;x<=n;x+=(x&-x))rt[x]=0;}
void del(int x)
{
    while(tcnt)ls[tcnt]=0,rs[tcnt]=0,tr[tcnt]=0,tcnt--;
    //for(int i=1;i<=n;i++)rt[i]=0;
    for(Nd it:v[x])
        if(it.id==0)dell(dfn[it.u]),dell(ed[it.u]+1);
}
void work(int x)//樹狀數組單點修改(差分),區間查詢(前綴和) //此處x是顏色
{
    //printf("x=%d\n",x);
    for(Nd it:v[x])
    {
        if(it.id==0)//如果是修改,則子樹內各點的線段樹上所染顏色位置+1
        {
            //printf("stchg\n");
            chg1(dfn[it.u],it.l,1);
            chg1(ed[it.u]+1,it.l,-1);
            //printf("aftchg\n");
        }
        else//如果是查詢
        {
            //printf("stq\n");
            int u=it.u,L=it.l,R=it.r,id=it.id;
            //printf("u=%d L=%d R=%d id=%d\n",u,L,R,id);
            int num=qry1(dfn[u],L,R),p=u;
            if(!num){ans[id]=-1; continue;}
            for(int i=19;i>=0;i--)
                if(qry1(dfn[fa[p][i]],L,R)==num)p=fa[p][i];
            //printf("u=%d p=%d %lld %lld\n",u,p,dis[u],dis[p]);
            ans[id]=dis[u]-dis[p];
            //printf("aftq\n");
        }
    }
    del(x);//刪除線段樹
}
int main()
{
    scanf("%d%d",&n,&q); init();
    for(int i=1,u,v,s;i<n;i++)
        scanf("%d%d%d",&u,&v,&s),add(u,v,s),add(v,u,s);
    dfs(1);
    //v[x]:x的0同余類下的修改與查詢操作,而且是按順序放的
    for(int i=1,tp,u,a,b,c;i<=q;i++)
    {
        scanf("%d%d%d",&tp,&u,&a);
        if(tp==0)
        {
            for(int it:ele[a])v[it].pb((Nd){u,a,0,0});//it作為詢問中的x時可查的點增加了
            ans[i]=-2;
        }
        else
        {
            scanf("%d%d",&b,&c);
            if(((b+1)/c)==((a-1)/c))ans[i]=-1;//(r+1)/x==(l-1)/x,即不存在l--r中的數mod x = 0
            else v[c].pb((Nd){u,a,b,i});
        }
    }
    for(int x=1;x<=n;x++)work(x);//此處x是顏色
    for(int i=1;i<=q;i++)
        if(ans[i]!=-2)
            if(ans[i]==-1)printf("Impossible!\n");
            else printf("%lld\n",ans[i]);
    return 0;
}
me

 

I:Kuriyama Mirai and Exclusive Or

題意:

(見題面)

分析:

(見題解

做異或的差分,區間修改就給兩個端點異或即可;

加長度的異或,可以按\( x \)的每一個二進制為\( 1 \)的位考慮;兩段之間異或值就一直是\( +0,+1,+2,+3,......,+2^{i-1} \),可以用\( chg[i][j] \)記錄一下;

然后\( chg[i][j] \)處理的時候又可以拆成\( chg[i][j-1], chg[i+2^{j-1}][j-1] \),然后給右半部分區間異或一個\( 2^{j-1} \);因為\( 0, 1, 2, ......, 2^{j-1} \)的左半部分和右半部分的差異僅僅是右半部分的二進制第\( j-1 \)位上都是\( 1 \)而已,異或了以后就可以拆分了。

寫得有點不好理解,詳細可以再看看題解和代碼。

代碼如下:

#include<iostream>
using namespace std;
int const N=6e5+5;
int n,q,a[N],sum[N];
bool chg[N][30];
void work(int pos,int x)
{
    for(int i=0,p2;i<30&&pos<=n;i++)
        if(x&(1<<i))
        {
            chg[pos][i]^=1;//從pos開始異或0,1,2,……,2^i-1(還可以分裂,下面操作)
            sum[pos]^=((x>>i)<<i);//val,第i位后面全是0  //差分——左端點+
            if((p2=pos+(1<<i))<=n)sum[p2]^=((x>>i)<<i);//差分——右端點-
            pos+=(1<<i);
            x+=(1<<i);//加了這一段的長度
        }
}
void work2()
{
    for(int i=29;i;i--)
        for(int j=1,p2,p3;j<=n;j++)
            if(chg[j][i])
            {
                chg[j][i-1]^=1;//分裂:對於0,1,2,……,2^i-1,若右半的所有數都去掉二進制最高位,則兩半是一樣的
                if((p2=j+(1<<(i-1)))<=n)
                {
                    chg[p2][i-1]^=1;//分裂之右半部分
                    sum[p2]^=(1<<(i-1));//差分——左端點+
                    if((p3=p2+(1<<(i-1)))<=n)sum[p3]^=(1<<(i-1));//差分——右端點-
                }
            }
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1,x;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1,tp,l,r,x;i<=q;i++)
    {
        scanf("%d%d%d%d",&tp,&l,&r,&x);
        if(!tp){sum[l]^=x; sum[r+1]^=x; continue;}
        work(l,x); work(r+1,x+(r-l+1));// l--n, r+1--n
    }
    work2();
    for(int i=1;i<=n;i++)
    {
        sum[i]^=sum[i-1];
        a[i]^=sum[i];
        printf("%d%c",a[i],(i==n)?'\n':' ');
    }
    return 0;
}
me

 

J:Counting Triangles

題意:

給出\(n\)個點的完全圖,每條邊是黑色或白色;求同色三角形的個數。\(1 \leq n \leq 8000\)。

分析:

\(1\)到\(n\),記錄每個點左邊、右邊的白邊點和黑邊點各有多少個,算出不同色三角形的個數;每個三角形被算了兩邊(三個點中有兩個點連了異色邊),最后除以二,再從所有三角形中減去即可。

G寫了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
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;
int n;
ll Tot,Ill;
bool Edge[8005][8005];
int Input()
{   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();
    return n;
}
int main()
{   n=Input(),Tot=1ll*n*(n-1)*(n-2)/6;
    for(int i=0;i<n;i++)
    {   ll Rb=0,Rw=0,Lb=0,Lw=0;
        for(int j=i-1;j>=0;j--) Edge[i][j]?Lb++:Lw++;
        for(int j=i+1;j<n;j++) Edge[i][j]?Rb++:Rw++;
        Ill+=Lb*Rw+Lw*Rb+Lw*Lb+Rw*Rb;
    }
    printf("%lld\n",Tot-Ill/2);
}
G

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM