1. 程式人生 > >[NOIP11.1模擬賽]補番報告

[NOIP11.1模擬賽]補番報告

trick 科學 其余 inline 與操作 struct ons 一個 times

Preface

昨天開始補某科學的超電磁炮S 感覺今天就好了點,炮姐賽高

T1 一開始一直想歐拉定理&ex歐拉定理,結果估計70分,數組開小了GG,看了正解發現是我學傻了

T2 一看就是數據結構,之前某次模擬還做過區間位運算線段樹但是不敢打,只敲了個前綴和預計50結果有個地方沒膜GG

T3 想了蠻久,很有意思的一道題目,考場上畫了圖發現我們可以將放坐標的那個點看成樹根,這樣它覆蓋了一條近似鏈的玩意,想搞波DP發現不會,於是打了個騙分的玩意,就是我猜和節點的兒子個數有關,我直接用兒子個數除2向上取整結果...OJ上15,source裏30.發現ZZ地沒判點的情況,然後發現更ZZ的是我特意判了鏈的情況,但是我輸出的是0

10分就這麽走了GG

T1 pow

學傻了的我一直想歐拉定理...結果solution告訴你只要會快速冪就可以A這題了

a既然是定值我們就分塊打表啊

由於b最大是1e12,按1e6拆分.

預處理出\({a^1}... {a^{1e6}}\),丟進一個數組,在預處理出\(a^{i \times 1e6}\),丟進另外一個數組

這樣所有的b都能拼出來了

還是比較妙的,這告訴我們有的時候去搞舊算法不如想新trick

代碼

const int maxn=1000005;
const int inf = 0x7fffffff;
ll a,q,k;
ll b,l,m,c,p;
ll pre[maxn][2],sum[10000005];
inline ll ksm(ll aa,ll cc){
    ll ans=1;
    while(cc){
        if(cc&1)ans=ans*a%p;
        aa=aa*aa%p;
        cc=cc>>1;
    }
    return ans;
}
int main(){
    FI(pow)FO(pow)
    read(a),read(p),read(q),read(k);
    read(b),read(l),read(m),read(c);
    pre[0][0]=1;
    for(ri i=1;i<=1000000;i++){
        pre[i][0]=pre[i-1][0]*a%p;
    }
    pre[1][1]=pre[1000000][0];
    for(ri i=2;i<=1000000;i++){
        pre[i][1]=pre[i-1][1]*pre[1][1]%p;
    }
    for(ri i=1;i<=q;i++){
        b=(b*m+c)%l;
        if(b<=1000000)sum[i]=sum[i-1]^pre[b][0];
        else {
            ll id=b/1000000;
            sum[i]=sum[i-1]^((pre[b%1000000][0]*pre[id][1])%p);
        }
    }
    int kk=k;
    while(kk<=q){
        printf("%lld\n",sum[kk]);
        kk+=k;
    }
    return 0;
}

T2 seg

英文是seg 中文是tree 就是告訴你用線段樹(segment tree)啦

對於區間按位與操作,類比區間取膜的時間復雜度分析(給學弟講過結果自己還忘了)

每個數最多操作31次,n只有1e5,因此是資瓷的

我們只要維護一個區間按位或判斷需不需要修改就好了,其余情況暴力遞歸修改

2操作就trival了,會線段樹的都會

3操作也比較正常,你可以先思考部分分怎麽拿,你把這個期望平方和(實際上這個式子就是\(\sum_{i=l}^r (a_i+a_j)^2 (j=l,l+1...r)\))大力展開

發現維護一個區間平方和和區間和就好了

然後有個神坑的地方

就是在判斷是否要按位與時的位運算的優先級

if((or_sum[now]&(~dta))==0)return ;

if(or_sum[now]&(~dta)==0)return ;

請您判斷上面哪個是對的

答案是第一個,但是你寫第二個不會對答案正確性產生影響,但是每次它都會遞歸下去修改使得時間復雜度大大提高

可見位運算優先級之低

代碼

const int maxn=100005;
const int inf=0x7fffffff;
const int P=998244353;
int a[maxn],n,q;
ll sum[maxn<<2];
int eq_sum[maxn<<2];
int or_sum[maxn<<2];
inline void up(int now){
    sum[now]=sum[now<<1]+sum[now<<1|1];
    eq_sum[now]=(eq_sum[now<<1]+eq_sum[now<<1|1]);
    if(eq_sum[now]>P)eq_sum[now]-=P;
    or_sum[now]=or_sum[now<<1]|or_sum[now<<1|1];
    return ;
}
void build(int now,int l,int r){    
    if(l==r){
        or_sum[now]=sum[now]=a[l];
        eq_sum[now]=1ll*a[l]*a[l]%P;
        return ;
    }
    int mid=(l+r)>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    up(now);return ;
}
int dta,t,L,R;
void update(int now,int l,int r){
    if((or_sum[now]&(~dta))==0)return ;//運算符順序!!! 
    if(l==r){
        a[l]=a[l]&dta;
        or_sum[now]=sum[now]=a[l];
        eq_sum[now]=1ll*a[l]*a[l]%P;
        return ;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(now<<1,l,mid);
    if(mid<R)update(now<<1|1,mid+1,r);
    up(now);return ;
}
ll ans1=0,ans2=0;
void query(int now,int l,int r){
    if(L<=l&&r<=R){
        ans1+=sum[now];
        ans2=(ans2+eq_sum[now]);
        if(ans2>P)ans2-=P;
        return ;
    }
    int mid=(l+r)>>1;
    if(L<=mid)query(now<<1,l,mid);
    if(mid<R)query(now<<1|1,mid+1,r);
    return ;
}
int main(){
    int x,y,opt;
    FO(seg)
    //freopen("seg6.in","r",stdin);
    //freopen("wtf.out","w",stdout);
    read(n);
    for(ri i=1;i<=n;i++)read(a[i]);
    build(1,1,n);
    read(q);
    while(q--){
        read(opt),read(L),read(R);
        ans1=ans2=0;
        if(opt==1){
            read(dta);
            update(1,1,n);
        }
        if(opt==2){
            query(1,1,n);
            printf("%lld\n",ans1);
        }
        if(opt==3){
            query(1,1,n);
            ans1=ans1%P;
            printf("%lld\n",(((ans2<<1)%P*(R-L+1)%P+(ans1<<1)*ans1%P))%P);
        }
    }
    return 0;
}

T3 beacon

一道Topcoder上有趣的題目

我是這麽想的,除了一點之外的情況,答案肯定是大與等於1的,我們不妨先欽定一個點,在上面放一個信標,然後以它為根遍歷整棵樹,顯然此時深度相同的點都是非法的

考慮這種情況:有兩個兄弟葉節點,那麽它們此時是非法的,顯然在除這兩個葉節點之外的任何一點放置信標這兩點還是非法的(它們的深度還是相同),所以我們必須在這兩個葉子節點之一放一個信標

稍微拓展一下:假若有三個兄弟葉節點,那麽類似的發現你必須在三個葉節點中放兩個信標才可以;於是歸納假設發現對於n個兄弟葉節點你必須在之中放n-1個信標

那麽對於不是葉節點的點呢?你會發現這時候它們似乎都已經是合法的了

除了鏈的情況,鏈的情況下由於最底端只有一個葉子節點不會統計答案,但是實際上你會發現鏈實際上整體就可以看做一個葉節點處理.

於是按照上面的步驟\(O(N^2)\)就好了

滿分做法看不懂,這裏給出題解,不知哪位大佬可以幫忙解釋一下

如何做到 O(n)? 我們先特判鏈的情況答案為 1, 然後找到任意一個度數大於 2 的節點, 可以證
明這個點一定不需要放置信標. 於是以這個點作根 O(n) 的貪心即可. 證明如下:
深度相同的點對證明同上, 只考慮深度不同的點對. 如果它們在一顆子樹中, 由於度數大於 2 所
以一定有另一顆子樹的一個信標把他們區分開. 如果在不同的子樹中, 有兩種情況:
一個在沒放信標的子樹中, 一個在放了的子樹中. 顯然還存在另一個子樹放了信標, 由於深度不
同他們會被這個信標區分開.
兩個都在放了信標的子樹中. 如果根的度數大於 3 則同上. 度數等於 3 時, 如果他們沒有被區分
開, 一定是他們先匯集到了一個節點上, 然後走到同一個信標上. 這個點一定是一條奇鏈的中點, 且
不是根 (由於深度不同), 是在兩個子樹之一中唯一的. 那麽他們走到另一個信標就一定有一個點走
了冤枉路, 既另一個信標可以區分出他們

70分代碼

const int maxn=1000005;
const int inf=0x7fffffff;
int n;
struct Edge{
    int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to){
    edge[++num_edge].ne=h[f];
    edge[num_edge].to=to;
    h[f]=num_edge;
}
int ans=inf,sum=0;
bool is_lef[maxn],on_chain[maxn];
void dfs(int now,int fa){
    int v,cnt=0,tot=0;
    is_lef[now]=0,on_chain[now]=0;
    for(ri i=h[now];i;i=edge[i].ne){
        v=edge[i].to;
        if(v==fa)continue;
        is_lef[now]=1,tot++;
        dfs(v,now);
        if(!is_lef[v]||on_chain[v])cnt++;
    }
    if(tot==1&&cnt==1)on_chain[now]=1;
    if(cnt>1)sum+=cnt-1;
    return ;
}
int deg[maxn];
int main(){
    int x,y;
    //FO(beacon)
    read(n);
    if(n==1){puts("0");return 0;}
    for(ri i=1;i<n;i++){
        read(x),read(y);
        add_edge(x,y),add_edge(y,x);
    }
    for(ri rt=1;rt<=n;rt++){
        sum=0;
        dfs(rt,0);
        ans=min(ans,sum+1);
    }
    printf("%d\n",ans);
    return 0;
}

[NOIP11.1模擬賽]補番報告