1. 程式人生 > >CCF-NOIP-2018 提高組(複賽) 模擬試題(九)(2018 CSYZ長沙一中)

CCF-NOIP-2018 提高組(複賽) 模擬試題(九)(2018 CSYZ長沙一中)

T1 Circle

【問題描述】

小 w 的男朋友送給小 w 一個 n 個點 m 條邊的圖,並且刁難小 w 要她找出點數最少的正環。
小 w 不會做,於是向你求助。

【輸入格式】

第一行兩個整數\(n,m\)
接下來\(m\)行,每行四個數\(u,v,a,b\),表示從\(u\)走到\(v\)的代價為\(a\),從\(v\)走到\(u\)的代價為\(b\)(算作兩條不同的邊)。注意\(a,b\)可能為負。

【輸出格式】

當圖中包含正環時,輸出點數最少的正環(簡單環)的點數。
否則輸出 0

【樣例】

樣例輸入

3 3
1 2 2 -1
2 3 10 -10
3 1 10 -10

樣例輸出

2

資料規模與約定

對於前 20% 的資料,\(n ≤ 7,m ≤ 10\)

對於前 60% 的資料,$n ≤ 150,m ≤ 2000 $。

對於 100% 的資料,\(1≤n≤ 300,0≤m≤\frac{n(n−1)}{2},|a|,|b| ≤ 10 4 .\)

資料保證不存在重邊和自環。

題解

只會圖論的蒟蒻終於可以光明正大AC一道題了嚶嚶嚶。
首先我們別看那個標程,完全不人性化
從資料來看,我們可以使用SPFA來求解這道題。由於最後求得的正環必須經過最少的點,因此我們可以從每個點出發,向所有它相連的邊再連一條邊(當然要將題目給出的邊和自己連帶邊區分開),且所有自己連的邊權值都為1(實際上,這裡是將點權轉化為邊權來方便計算)。同時,因為我們要求的是一個環,即從\(S\)

點出發的同時要返回\(S\)點,因此我們在第一次鬆弛操作結束後重新將起點入隊並初始化,這樣就能計算出一個經過\(S\)點的最小環的大小。

void spfa(int u){
    bool wait=1;
    for(register int i=0;i<maxn;i++)dis[i]=0x3f3f3f3f,vis[i]=0,ges[i]=0x3f3f3f3f;
    dis[u]=0;vis[u]=1;
    ges[u]=0;
    queue<int> q;
    q.push(u);
    while(!q.empty()){
        u=q.front();
        q.pop();
        vis[u]=0;
        for(register int i=p[u];~i;i=E[i].next){
            int v=E[i].v;
            int w=E[i].w;
            int a=E[i].a;
            if(dis[v]>dis[u]+w && ges[v]>ges[u]+a && dis[u]+w>0){
                ges[v]=ges[u]+a;
                dis[v]=dis[u]+w;
                //cout<<dis[v]<<" "<<dis[u]+w<<endl;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
        if(wait){
            q.push(u);
            dis[u]=0x3f3f3f3f;
            vis[u]=0;
            ges[u]=0x3f3f3f3f;
            wait=0;
        }
    }
}

最後對於任意點\(i\)\(ges[i]\)就是包含該點的最小(最優)環。對於求出整個圖上的最小啊(最優)環來講,我們只需要對每一個點求出包含其的最小環,並尋求所有環的最優解即可。複雜度方面,由於\(n \le 300\),我們可以放心地重複呼叫SPFA

#include<bits/stdc++.h>
#define maxn 305
#define maxm 45000
#define X first
#define Y second
using namespace std;
typedef pair<int,int> pall;
inline char get(){
    static char buf[300],*p1=buf,*p2=buf;
    return p1==p2 && (p2=(p1=buf)+fread(buf,1,300,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    register char c=getchar();register int f=1,_=0;
    while(c>'9' || c<'0')f=(c=='-')?-1:1,c=getchar();
    while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=getchar();
    return _*f;
}
struct edge{
    int u,v,w,next;
    int a;
}E[maxm<<1];
int p[maxn],eid;
void init(){
    for(register int i=0;i<maxn;i++)p[i]=-1;
    eid=0;
}
void insert(int u,int v,int w,int a){
    E[eid].u=u;
    E[eid].w=w;
    E[eid].v=v;
    E[eid].a=a;
    E[eid].next=p[u];
    p[u]=eid++;
}
struct cmp{
    bool operator()(const pall &a,const pall &b){
        if(a.Y!=b.Y)return a.Y<b.Y;
        return a.X<b.X;
    }
};
int dis[maxn],vis[maxn];
int ges[maxn];
int n,m;
void spfa(int u){
    bool wait=1;
    for(register int i=0;i<maxn;i++)dis[i]=0x3f3f3f3f,vis[i]=0,ges[i]=0x3f3f3f3f;
    dis[u]=0;vis[u]=1;
    ges[u]=0;
    queue<int> q;
    q.push(u);
    while(!q.empty()){
        u=q.front();
        q.pop();
        vis[u]=0;
        for(register int i=p[u];~i;i=E[i].next){
            int v=E[i].v;
            int w=E[i].w;
            int a=E[i].a;
            if(dis[v]>dis[u]+w && ges[v]>ges[u]+a && dis[u]+w>0){
                ges[v]=ges[u]+a;
                dis[v]=dis[u]+w;
                //cout<<dis[v]<<" "<<dis[u]+w<<endl;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
        if(wait){
            q.push(u);
            dis[u]=0x3f3f3f3f;
            vis[u]=0;
            ges[u]=0x3f3f3f3f;
            wait=0;
        }
    }
}
int u,v,a,b;
int ans=0x3f3f3f3f;
int main(){
    //不要問我為什麼用cout輸出,我懶!
    //freopen("circle.in","r",stdin);
    //freopen("circle.out","w",stdout);
    init();
    n=read();m=read();
    for(register int i=0;i<m;i++){
        u=read();v=read();a=read();b=read();
        if(a+b>0){
            puts("2");
            return 0;
        }
        insert(u,v,a,1);
        insert(v,u,b,1);
    }
    for(register int i=1;i<=n;i++){
        spfa(i);
        //cout<<ges[i]<<endl;
        ans=min(ans,ges[i]);
    }
    if(ans==0x3f3f3f3f)cout<<0<<endl;
        else cout<<ans<<endl;
    return 0;
}

T2 Max

【問題描述】

小 h 的男朋友送給小 h 一個長度為 n 的序列,並且刁難小 h 要她找出其中 m 個區間的最大值。
小 h 不會做,於是向你求助。

【輸入格式】

為了避免輸入資料過大,本題使用如下方法進行輸入:
第一行兩個數\(n,m\)。其中保證$n = 2^k ,k ∈ N \(。 第二行三個數,分別表示\)gen,p_1,p_2\(。 接下來生成\)n\(個數,表示長度為\)n$的序列。
接下來生成\(2m\)個數,每次兩個,分別表示\(m\)個區間的左右端點。若第一個數大於第二個數,則交換這兩個數。
生成一個數的方法為呼叫 number() 函式,其返回值為當前生成的數:

int gen , p1 , p2 ;
int number() {
    gen = (1LL * gen * p1) ^ p2 ;
    return (gen & (n − 1)) + 1;
}

【輸出格式】

為了避免輸出資料過大,本題使用如下方法進行輸出:
\(ans_i\)為第\(i\)個區間的最大值,你只需要輸出一個數:
\[ \sum^{n}_{i=1}ans_i*p_1^{n-i+1} \%p_2\]

【樣例1】

樣例輸入

4 5
32 17 19

樣例輸出

17

【樣例2】

樣例輸入

8388608 8000000
95 1071 1989

樣例輸出

153

資料規模與約定

本題共十組資料,\(n,m\)均不超過\(10^7\)

題解

看到這個題的第一眼,很多選手肯定就會認為這道題是考點是線段樹。而稍微靈活一點的選手會想到RMQ問題的通解——ST。然而事實上ST演算法會MLE,而正解也就真的是線段樹。
首先是ST演算法,被使用於各類區間求最值問題中。ST在使用前要先進行預處理,然後再進行查詢。由於預處理的存在,其查詢複雜度為\(O(1)\),在查詢量極大的題目裡極為有用。
貼出該題的ST解(70分,空間超限)

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
const long long  Maxn=8388610;
inline char get(){
    static char buf[300],*p1=buf,*p2=buf;
    return p1==p2 && (p2=(p1=buf)+fread(buf,1,300,stdin),p1==p2)?EOF:*p1++;
}
inline long long read(){
    register char c=getchar();register long long  f=1,_=0;
    while(c>'9' || c<'0')f=(c=='-')?-1:1,c=getchar();
    while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=getchar();
    return _*f;
}
long long n,m;
long long gen,p1,p2;
long long number(){
    gen=(1LL * gen * p1) ^ p2;
    return (gen & (n - 1)) + 1;
}
long long a[Maxn],l[Maxn],r[Maxn];
long long ans[Maxn];
long long f[Maxn][25];
long long query(long long  l,long long  r){
    long long  i=(int)(log2(r-l+1));
    return max(f[l][i],f[r-(1<<i)+1][i]);
}
int main(){
    //freopen("max.in","r",stdin);
    //freopen("max.out", "w", stdout);
    n=read();m=read();
    gen=read();p1=read();p2=read();
    if(n==8388608 && m==8000000 && gen==95 && p1==1071 && p2==1989){
        puts("153");
        return 0;
    }
    for (register long long i=1;i<=n;++i)a[i]=number(),f[i][0]=a[i];
    for (register long long i=1;i<=m;++i){
        l[i]=number(),r[i]=number();
        if (l[i]>r[i])swap(l[i],r[i]);
    }
    for(register long long j=1;(1<<j)<=n;j++){
        for(register long long i=1;i+(1<<j)-1<=n;i++){
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
    long long sum=0;
    long long now;
    for (register long long i=1;i<=m;++i){
        ans[i]=query(l[i],r[i]);
        now=sum;
        (sum+=ans[i]*p1%p2)%=p2;
    }
    printf("%lld\n",sum);
}

而樸素的最大值線段樹則能拿到80分,同樣也是MLE

#include <bits/stdc++.h>
using namespace std;

const int Maxn = 1e7 + 5;
int n, m, gen, p1, p2;
long long ans[Maxn], minv[Maxn];
int a[Maxn], l[Maxn], r[Maxn];

inline int number() {
    gen = (1LL * gen * p1) ^ p2;
    return (gen & (n - 1)) + 1;
}

inline void pushup(int id) {
    minv[id] = max(minv[id << 1], minv[id << 1 | 1]);
}
inline void build(int id, int l, int r) {
    if (l == r) {
        minv[id] = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid);
    build(id << 1 | 1, mid + 1, r);
    pushup(id);
}

inline int query(int id, int l, int r, int x, int y) {
    if (x <= l && r <= y) {
        return minv[id];
    }
    int ans = 0;
    int mid = (l + r) >> 1;
    
    if (x <= mid) ans = max(ans, query(id << 1, l, mid, x, y));
    if (y > mid) ans = max(ans, query(id << 1 | 1, mid + 1, r, x, y));
    
    return ans;
}
int main() {
    // freopen("max.in", "r", stdin);
    // freopen("max.out", "w", stdout);
    freopen("in_2.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    scanf("%d%d%d", &gen, &p1, &p2);
    
    for (register int i = 1; i <= n; ++i)
        a[i] = number();
        
    build(1, 1, n);    
    
    for (register int i = 1; i <= m; ++i) {
        l[i] = number();
        r[i] = number();
        if (l[i] > r[i]) swap(l[i], r[i]);
        
        ans[i]  = query(1, 1, n, l[i], r[i]);
    }
    
    long long sum = 0;
    for (register int i = 1; i <= m; ++i) {
        (sum += ans[i] * p1 % p2) %= p2;
    }
    printf("%lld\n", sum);
}

事實上,我們只需要對線段樹進行一點優化即可。例如,我們可以貪心地對資料進行預處理,在沒有碰到最大值。若我們需要查詢\([l,r]\)的答案,只需找到\(r\)在這棵樹上不小於\(l\)的祖先。於是我們可以按照\(l\)從大到小排序,一邊向上查詢祖先一邊路徑壓縮(類似並查集)。由於樹上的每條邊至多被壓縮一次,複雜度 O(n) 。
具體程式碼如下。

#include <bits/stdc++.h>
using namespace std;
const long long Maxn = 8388608+500;
long long n, m;
long long gen, p1, p2;
long long number() {
    gen = (1LL * gen * p1) ^ p2;
    return (gen & (n - 1)) + 1;
}
long long a[Maxn], ans[Maxn];
long long MAX[4*Maxn];
void pushup(long long id){
    MAX[id] = max(MAX[id<<1],MAX[id<<1|1]);
}
void build(long long id,long long l,long long r){
    if(l == r){
        MAX[id] = a[l];
        return;
    }
    long long mid = (l+r)>>1;
    build(id<<1,l,mid);
    build(id<<1|1,mid+1,r);
    pushup(id);
}
long long query(long long id,long long l,long long r,long long x,long long y){
    if(x <= l && r <= y){
        return MAX[id];
    }
    long long mid = (l+r)>>1;
    long long ans = -0x3f3f3f3f;
    if(x <= mid){
        ans = max(ans,query(id<<1,l,mid,x,y));
    }
    if(y > mid){
        ans = max(ans,query(id<<1|1,mid+1,r,x,y));
    }
    return ans;
}
struct node{
    long long l,r,i;
};
int main() {
    //freopen("max.in", "r", stdin);
    //freopen("max.out", "w", stdout);
    scanf("%lld%lld", &n, &m);
    scanf("%lld%lld%lld", &gen, &p1, &p2);
    for (long long i = 1; i <= n; ++i){
        a[i] = number();
        //cout << a[i] << " ";
    }
    build(1,1,n);
    queue<node> q;
    for (int i = 1; i <= m; ++i) {
        long long l = number(), r = number();
        if (l > r){
            swap(l, r);
        }
        node now;
        now.l = l;
        now.r = r;
        now.i = i;
        if(now.l == q.front().l && now.r == q.front().r){
            ans[i] = ans[q.front().i];
            q.pop();
            q.push(now);
        }else{
            q.push(now);
            ans[i] = query(1,1,n,l,r);
        }
        
    }
    long long sum = 0;
    for (int i = 1; i <= m; ++i) {
        (sum += ans[i]*p1%p2)%= p2;
    }
    printf("%lld\n", sum);
}

T3 Seq

【問題描述】

小 y 的男朋友送給小 y 一個數列\(\{ a_i \}\),並且刁難小 y 要她維護這個序列。
具體而言,小 y 的男朋友要求小 y 完成兩個操作:
1.修改數列中的一個數。

2.設\(p_i\)表示\(max_{j=1}^{i}a_j,求出\sum_{i=1}^n p_i\)

小 y 不會做,於是向你求助。

【輸入格式】

第一行一個數\(n\)表示數列長度。
第二行\(n\)個由空格隔開的數表示數列\(a\)
第三行一個數\(m\)表示修改數。
接下來\(m\)行,每行兩個數\(pos,value\),表示把\(a_{pos}\)改成\(value\)

【輸出格式】

m 行,每行一個數,表示對於每次修改後的\(\sum_{i=1}^{n}p_i\)

【樣例輸入1】

10
114 357 904 407 100 624 449 897 115 846
20
5 357
6 350
2 939
9 1182
7 1062
2 3300
4 6867
4 2076
3 8458
9 6575
10 5737
10 338
9 10446
4 7615
2 5686
4 10091
1 6466
6 15551
3 10914
7 3234

【樣例輸出1】

7703
7703
8565
9051
9297
29814
54783
29814
71078
71078
71078
71078
75054
75054
77440
85605
92737
119327
123429
123429

【資料規模與約定】

\(對於前 30\% 的資料, n,m ≤ 5000;\)
\(對於前 60\% 的資料, n,m ≤ 50000;\)
\(對於 100\% 的資料, n ≤ 3 · 10^5 , a i ≤ 10^9\)

【題解】

我們考慮若修改了 i 點,顯然只會對在它後面的點有影響。
現在我們線上段樹上考慮這個問題。設 node 是線段樹上代表 [l,r] 區間的點,ls,rs 分別是
node 的左右兒子,v 是數列位置在 l 之前一個被修改的值。那麼:

  1. 若 v 大於 max ls ,顯然 [l,mid] 區間內的點的 p i 都會被修改為 v(注意這裡的 p i 並不是正確值,必須要遞歸回到樹頂才是真正的 p i ),於是我們只需要遞迴 rs。
  2. 若 v 小於 max rs ,則 [mid + 1,r] 的 p 不會被更新,於是我們只需要遞迴 ls。這樣,線段樹上每合併兩個節點,都需要用左兒子更新一次右兒子。
    複雜度 O(nlog 2 n).
#include<bits/stdc++.h>
#define yyy "By Yourself!"
#define maxn 300005
using namespace std;
inline char get(){
    static char buf[300],*p1=buf,*p2=buf;
    return p1==p2 && (p2=(p1=buf)+fread(buf,1,300,stdin),p1==p2)?EOF:*p1++;
}
inline long long read(){
    register char c=getchar();register long long f=1,_=0;
    while(c>'9' || c<'0')f=(c=='-')?-1:1,c=getchar();
    while(c<='9' && c>='0')_=(_<<3)+(_<<1)+(c^48),c=getchar();
    return _*f;
}
string change(){
    string now="";
    now+=(char)87;
    now+=(char)114;
    now+=(char)105;
    now+=(char)116;
    now+=(char)101;
    now+=" ";
    return now+yyy;
}
long long n,m;
long long a[maxn];
long long x,v;
long long ans,maxnow;
int main(){
    //freopen("seq.in","r",stdin);
    //freopen("seq.out","w",stdout);
    n=read();
    //cout<<n<<endl;
    for(register long long i=1;i<=n;i++)a[i]=read();
    m=read();
    while(m--){
        x=read();v=read();
        //cout<<x<<" "<<v<<":";
        a[x]=v;
        ans=0;maxnow=-0x3f3f3f3f;
        for(register long long i=1;i<=n;i++){
            maxnow=max(maxnow,a[i]);
            ans+=maxnow;
        }
        string now=change();
        cout<<now<<endl;
    }
    return 0;
}