1. 程式人生 > >10-4國慶節第七場模擬賽題解

10-4國慶節第七場模擬賽題解

一次 .com spa space using ans -m 增加 直接

10-4 國慶節第七場模擬賽題解

T1工廠 (factory)

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
const int wx=10017;
int n,s,ans;
int f[wx],a[wx],c[wx];
signed main(){
    freopen("factory.in","r",stdin);
    freopen("factory.out","w",stdout);
    n=read();s=read();
    for(int i=1;i<=n;i++){
        c[i]=read();a[i]=read();
    }
    f[1]=c[1];
    ans+=(f[1]*a[1]);
    for(int i=2;i<=n;i++){
        f[i]=min(f[i-1]+s,c[i]);
        ans+=(f[i]*a[i]);
    }
    printf("%lld\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

T2鐵路 (trainfair)

Description

在 MS 國有 N 座城市,編號從 1 到 N ,其中 1 號城市是 MS 國的首都。

MS 國裏,只有一家鐵路公司,經營著 M 條連接著各城市的鐵路線路,每條線路都雙向地連接著兩座不同的城市。通過這些線路,我們可以在任意兩座城市間通行。

原本,乘坐一條線路只需要花費 1 角錢。可是由於經營不善,鐵路公司提出計劃,要在今後的 Q 年間,每年給某一條線路漲價為 2 角錢。保證一條線路不會被漲價多次。

另外,這個鐵路公司每年都會在每一座城市進行一次居民滿意度調查。原本每一座城市中的居民都對鐵路公司的服務十分滿意,但在線路漲價後就說不定了。每一年的滿意度調查都會在當年的漲價計劃實施後進行。如果一座城市中的居民在當年坐火車去首都所需的最少花費相比於計劃提出前增加了,他們就會對鐵路公司抱有不滿。首都的居民永遠不會對鐵路公司抱有不滿。

在計劃施行前,你需要幫助鐵路公司統計出未來的每一年中,各將有多少城市的居民對鐵路公司抱有不滿。

Input

輸入文件第一行包含三個正整數 N, M, Q,分別表示 MS 國的城市數量、鐵路路線數量和漲價計劃將要實施的時間長度。

接下來 M 行,其中第 i行包含 22 個整數 Ui,Vi,表示第 ii 條路線連接著編號為 Ui 和 Vi 的兩座城市。

接下來 Q 行,其中第j 行包含一個整數 Rj,表示計劃施行後第 j 年將會讓第 Rj 條線路漲價。

Output

輸出 Q 行,其中第 j 行表示第 j 年居民對鐵路公司抱有不滿的城市的數量。

最短路擁有一個性質:

對於x與y之間的一條邊設為z,那麽如果dis[x]=dis[y]+edge[z].dis或者dis[y]=dis[x]+edge[z].dis,那麽我們就將這種邊單獨挑出,那麽所有滿足這種情況的邊所組成的圖是一個拓撲圖。

針對這個性質搞事情,分析一下這道題,將邊權從一角長為兩角其實就相當於把這條邊刪去。因為他對後面的點的到點1的最短路的改變的情況和刪去是等價的。

那麽我們就可以把所有滿足上述性質的邊拿出來建出一個拓撲圖。

為什麽要把這些邊拿出來呢?

技術分享圖片

對於上圖的紅色邊,顯然他並不是符合上述性質的邊,即不是最短路上的邊,那麽如果這條邊變化了,對任何的點都不會有任何影響。

換一種角度來說,每一個點和一的最短路,都只會因為從一到這個點的最短路上的邊的改變而改變,所以做法就很明了了。

把所有的最短路上的邊拿出組成一個拓撲圖,開始輸入每一條要刪掉的邊,去找這條邊的兩個端點中距離1更遠的點,那麽在判斷完這條邊是最短路上的邊上後,我們就可以把這個點的入度減一了,同時把這條邊的標記清零。

那麽如果這個點的入度減為了零,那麽就說明這個點沒有辦法從其他的路徑走最短路到1了,所以這時的全局增量ans就可以加一了。

我們又可以想到,如果刪除了這個點,那麽肯定會影響到後面的點,所以我們就要從這一個點開始向後延伸,也就是把這個點所有的出邊全部刪去,在判斷會不會有新的點的入度變為0,使得ans++,再繼續向下。

因為每個點只能被刪一次,所以復雜度:O(n)

code:

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int wx=200017;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
struct e{
    int nxt,to,dis,flag,he;
}edge[wx*2];
int head[wx],dep[wx],in[wx],vis[wx];
int num,n,m,q,ans,x,y,z;
void add(int from,int to,int dis){
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].he=from;
    edge[num].dis=dis;
    head[from]=num;
}
queue<int > qq;
void Dij(int u){
    for(int i=1;i<=n;i++)dep[i]=2147483642,vis[i]=0;
    dep[1]=1;qq.push(1);vis[1]=1;
    while(qq.size()){
        int u=qq.front();qq.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            if(dep[v]>dep[u]+edge[i].dis){
                dep[v]=dep[u]+edge[i].dis;
                if(!vis[v]){
                    vis[v]=1;
                    qq.push(v);
                }
            }
        }
    }
}
void mark(){
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to;
            if(dep[u]>dep[v])continue;
            if(dep[u]==dep[v]-1){
                edge[i].flag=1;in[v]++;
            }
        }
    }
}
int find(int now){
    int re=0;
    for(int i=head[now];i;i=edge[i].nxt){
        if(!edge[i].flag)continue;
        int v=edge[i].to;if(!in[v])continue;
        if(dep[v]<dep[now])continue;
        in[v]--;edge[i].flag=0;
        if(!in[v])re+=find(v)+1;
    }
    return re;
}
int main(){
    freopen("trainfair.in","r",stdin);
    freopen("trainfair.out","w",stdout);
    n=read();m=read();q=read();
    for(int i=1;i<=m;i++){
        x=read();y=read();add(x,y,1);add(y,x,1);
    }
    Dij(1);
    mark();
    for(int i=1;i<=q;i++){
        x=read();
        if(dep[edge[x*2].he]<=dep[edge[x*2].to]&&edge[x*2].flag){
            x*=2;
            int now=edge[x].to;if(!in[now])goto zz;
            in[now]--;edge[x].flag=0;
            if(!in[now])ans+=find(now)+1;
        }
        else if(dep[edge[x*2-1].he]<=dep[edge[x*2-1].to]&&edge[x*2-1].flag){
            x=x*2-1;
            int now=edge[x].to;if(!in[now])goto zz;
            in[now]--;edge[x].flag=0;
            if(!in[now])ans+=find(now)+1;
        }
        zz:;
        printf("%d\n",ans);
    }
    return 0;
}

T3 數學題 (math)

Description

給定一個長度為 n 的序列 a,請你算一算這個式子:

\(\sum\limits_{i=1}^n\sum\limits_{j=i}^n(j-i+1)\min(a_i,a_{i+1},\dots,a_{j})\max(a_i,a_{i+1},\dots,a_{j})\).

由於高精度寫起來太麻煩了,請將答案對 10^9109 取模後輸出。

Input

  • 輸入文件第一行包含兩個數字 n, 表示序列 a 的長度。
  • 接下來一行包含 n 個數字,依次表示序列中的元素a1,a2,...,an。

Output

輸出式子的值對 \(10^9\) 取模後的結果。

對於這道題:每一次把區間分成等長的兩個小區間。每一次不考慮被包括在當前區間的左區間或者右區間的小區間的答案,只考慮l和r跨過當前大區間的mid的小區間的答案。因為這個時候不求的話,按照我們的求解策略,這個區間下一次一定會被分開,那麽就導致一部分答案被忽略了。

那麽主要任務就是給你一個區間:

? 左端點的範圍是l到mid,右端點的範圍是mid+1到r

? 所以求解答案為:
\[ \sum_{i=l}^{mid}\sum_{j=mid+1}^r (j-i+1)*max(a[i]……a[j])*min(a[i]……a[j]) \]
考慮枚舉所求區間的左端點。

cdq分治的思想有一部分就是對於求取答案,我們必須通過比較暴力的方式去求去出一些有用的信息。為了方便,把這個區間分成左右兩個區間,然後我們在左區間去找有效信息,再對應到右區間去實現更新答案,這也是為什麽只會在當前區間計算跨過mid的區間的答案。

所以對於這道題,我們枚舉小區間的左端點,那麽可以在枚舉的過程中確定從i到mid的最大值和最小值記為maxl和minl,那麽為了左區間有效信息充分利用的原則,我們就要考慮右端點j在什麽範圍可以使得這兩個量可以被利用到。

那就開始分析吧:

現在我們有兩個量maxl和minl,他們都會有兩個狀態,就是可以被用和不可以被用。

那麽就有三種情況:一是兩者都可以用,二是兩者只有一者可以用,三是兩者都不可以用。

並且我們已知maxl和minl確切的值,並且在我們從mid向前枚舉i的時候,當前的maxl和minl都是後綴最值,所以是具有單調性的。

同樣的,我們設A作為一個下標滿足:\(a[A]<=maxl &&a[A+1]>maxl\)

? 設B作為一個下標滿足:\(a[B]>=minl&&a[B+1]<minl\)

那麽通過A和B就可以確定maxl和minl在哪個範圍起作用。

所以現在的任務就是找到A和B。

因為A,B的權值是前綴極值,並且我們從前向後找,所以是滿足單調性的,完全可以二分找一下。

不過這裏我偷了一下懶直接暴力找的A,B,(好吧其實我不會。。。),也是可以的。

找到A,B之後,我們就會發現A和B將mid到r這段區間分成了三段對應了maxl和minl的適用範圍。

這裏我們假設A<B;

技術分享圖片

那麽現在對這三段進行分析。

一,當\(mid+1\leq j&&j \leq A\),這個時候區間i到j的最大值和最小值就是maxl和minl,所以就可以寫出答案公式:
\[ ans+=\sum_{j=mid+1}^A (j-i+1)*maxl*minl \\=maxl*minl*\sum_{j=mid+1}^A(j-i+1) \\=maxl*minl*((A-mid+1)*(mid-i)+(mid-i)*(mid-i-1)/2)//等差數列O(1)求 \]
二,當\(A+1 \leq j&&j\leq B\),這個時候區間i到j的最大值就不會是maxl了,因為這就是A的定義。但是minl還是可以作為區間最小值的。

所以當我們找到一個對應的j的時候,就需要一個到j的前綴最大值。公式:
\[ ans+=\sum_{j=A+1}^B(j-i+1)*minl*f(j) \\=minl*\sum_{j=A+1}^B(j-i+1)*f[j] \\=minl*(\sum_{j=A+1}^Bj*f(j)-(i-1)*\sum_{j=A+1}^Bf(j)) \]
可以很清晰的看到我們只要維護好前綴和就可以O(1)得來求這部分的答案了。

另外,我們當前討論的是A<B,那麽對於B<A的情況也是要考慮的,判斷一下就好,思路一致。

三,當\(B+1 \leq j &&j\leq r\)時,這個時候區間i,j的最大值和最小值就都不是maxl和minl了,那麽仿照二的想法,也是很容易想出公式的。
\[ ans+=\sum_{j=B+1}^r(j-i+1)*g(j)*f(j) \\=\sum_{j=B+1}^rj*g(j)*f(j)-(i-1)*\sum_{j=B+1}^rg(j)*f(j) \]
在分別維護前綴和就好了。

註意邊界,註意初始化,註意取模,這道題真的很好,但是真的惡心。

code:

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int mod=1e9;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
const int wx=1000017;
int a[wx];
int s[wx],smx[wx],smn[wx],ss[wx],ssl[wx],smxl[wx],smnl[wx],mx[wx],mn[wx];
int n,ans;
void work(int l,int r){
    if(l==r){
         ans=(ans+(a[l]*a[r])%mod)%mod;return;
    }
    int mid=l+r>>1;
    work(l,mid);work(mid+1,r);
    mn[mid]=0x3f3f3f3f;
    s[mid]=smx[mid]=smn[mid]=mx[mid]=ss[mid]=ssl[mid]=smxl[mid]=smnl[mid]=0;
    for(int i=mid+1;i<=r;i++){
        mx[i]=max(mx[i-1],a[i]);//f[i]存前綴最小值
        mn[i]=min(mn[i-1],a[i]);//g[i]存前綴最大值 
        s[i]=(s[i-1]+i-mid)%mod;
        ss[i]=(ss[i-1]+mn[i]*mx[i])%mod;
        smx[i]=(smx[i-1]+mx[i])%mod;
        smn[i]=(smn[i-1]+mn[i])%mod;
        ssl[i]=(ssl[i-1]+mx[i]*mn[i]%mod*(i-mid))%mod;
        smxl[i]=(smxl[i-1]+mx[i]*(i-mid))%mod;
        smnl[i]=(smnl[i-1]+mn[i]*(i-mid))%mod;
    }
    int A=mid,B=mid;
    int maxl=0,minl=0x3f3f3f3f;
    for(int i=mid;i>=l;i--){
        maxl=max(maxl,a[i]);
        minl=min(minl,a[i]);
        while(B<r&&mx[B+1]<maxl)B++;//B維護最後一個小於等於Max的位置;
        while(A<r&&mn[A+1]>minl)A++;//A維護最後一個大於等於Min的位置;
//      if(A<=B){
//          ans=(ans+maxl*minl%mod*((mid-i)*(A-mid+1)+(mid-i)*(mid-i-1)/2)%mod)%mod;
//          ans=(ans+maxl*(sumff[B]-(i-1)*sumf[B])%mod-maxl*(sumff[A-1]-(i-1)*sumf[A-1])%mod)%mod;
//          ans=(ans+(Sum[B]-(i-1)*SUm[B])-(Sum[A-1]-(i-1)*SUm[A-1]))%mod;
//      } 
//      else {
//          ans=(ans+maxl*minl%mod*((mid-i)*(B-mid+1)+(mid-i)*(mid-i-1)/2))%mod;
//          ans=(ans+minl*(sumgg[A]-(i-1)*sumg[A])%mod-minl*(sumgg[B-1]-(i-1)*sumg[B-1])%mod)%mod;
//          ans=(ans+(Sum[A]-(i-1)*SUm[A])-(Sum[B-1]-(i-1)*SUm[B-1]))%mod;
//      }
        int x=min(A,B),y=max(A,B);
        int ii=mid-i+1;
        ans=(ans+(s[x]+ii*(x-mid))*maxl%mod*minl)%mod;
        if(B<=A)
            ans=(ans+(smxl[y]-smxl[x]+ii*(smx[y]-smx[x]))%mod*minl)%mod;
        else
            ans=(ans+(smnl[y]-smnl[x]+ii*(smn[y]-smn[x]))%mod*maxl)%mod;
        ans=(ans+(ssl[r]-ssl[y]+ii*(ss[r]-ss[y])))%mod;
    }
}
signed main(){
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    work(1,n);
    printf("%lld\n",(ans+mod)%mod);
    return 0;
}

10-4國慶節第七場模擬賽題解