HNOI2018簡要題解

HNOI2018簡要題解

D1T1 尋寶遊戲

題意

某大學每一年都會有一次 Mystery Hunt 的活動,玩家須要根據設置的線索解謎,找到寶藏的位置,前一年獲勝的隊伍能夠得到這一年出題的機會。ios

做爲新生的你對這個活動很是感興趣。你天天都要從西向東通過教學樓一條很長的走廊,這條走廊是如此的長,以致於它被人戲稱爲 infinite corridor。一次,你通過這條走廊的時,注意到在走廊的牆壁上隱藏着 \(n\) 個等長的二進制的數字,長度均爲 \(m\)。你從西向東將這些數字記錄了下來,造成一個含有 \(n\) 個數的二進制數組 \(a_1, a_2, ..., a_n\)。很快,在最新的一期 Voo Doo 雜誌上,你發現了 \(q\) 個長度也爲 \(m\) 的二進制串 \(r_1, r_2, ..., r_q\)。聰明的你很快發現了這些數字的含義。保持數組 \(a_1, a_2, ..., a_n\) 的元素順序不變,你能夠在它們之間插入 \(\wedge\)(按位與運算)或者 \(\vee\)(按位或運算)兩種二進制運算符。例如:\(11011 \wedge 00111=00011,11011 \vee 00111=11111\)數組

你須要插入剛好 \(n\) 個運算符,相鄰兩個數之間剛好一個,在第一個數的左邊還有一個。若是咱們在第一個運算符的左邊補入一個 \(0\),這就造成了一個運算式,咱們能夠計算它的值。與往常同樣,運算順序是從左往右。有趣的是,出題人已經告訴你這個值的可能的集合——Voo Doo 雜誌裏的那一些二進制數 \(r_1, r_2, ..., r_q\),而解謎的方法,就是對 \(r_1, r_2, ..., r_q\) 中的每個值 \(r_i\),分別計算出有多少種方法填入這 \(n\) 個運算符,使得這個運算式的值是 \(r_i\) 。然而,infinite corridor 真的很長,這意味着數據範圍可能很是大。所以,答案也可能很是大,可是你發現因爲謎題的特殊性,你只須要求答案模 \(1000000007\)\(10^9 + 7\),一個質數)的值。網絡

對於 \(10\%\) 的數據,\(n \le 20, m \le 30\)\(q = 1\)數據結構

對於另外 \(20\%\) 的數據,\(n \le 1000\)\(m \le 16\)測試

對於另外 \(40\%\) 的數據,\(n \le 500\)\(m \le 1000\)優化

對於 \(100\%\) 的數據,\(1 \le n \le 1000\)\(1 \le m \le 5000\)\(1 \le q \le 1000\)ui

題解

orz myy. 神題。spa

發現\(|1\)\(\& 0\)後的結果是必定的,因此某一位最後爲1,則要求最後一個&0的位置要在|1以前。
聽說這樣從後往前爆搜,及時break能夠獲得70分?!code

而後考慮把操做序列量化成01串,&=1,|=0,則對於某一位來講,從後往前,當操做串的字典序小於運算元素的串,則最後運算結果爲1。排序

這樣就很好處理了,把這m個串摳出來,操做串要小於其中一些字串的字典序,大於等於另外一些的。也就是\(x\le op<y\),把\(x,y\)轉成二進制數後算差就是op的數量了。對這些串排序後算相鄰兩數差,最後要麼沒有答案要麼是某相鄰兩數之差。

注意考場沒有開O2因此最好用Trie樹排序或者雞排。

複雜度\(\mathcal O(nm)\)

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<vector>
#define pb push_back
using namespace std;
const int N=1100,M=5100,Tr=5e6+10,mod=1e9+7;
int n,m,q,nod=1,ch[Tr][2],rk[M],tt,bit[N],d[M];
char s[N][M];
vector<int> tag[Tr];
struct Num
{
    int s[N],rk;
    int operator - (Num A) const
        {
            int res=0;
            for(int i=n;i>=1;i--)
                if(s[i]!=A.s[i])
                    (res+=1ll*(s[i]-A.s[i]+mod)*bit[n-i+1]%mod)%=mod;
            return res;
        }
}A[M];
void Insert(Num A,int id)
{
    int x=1;
    for(int i=1;i<=n;i++)
    {
        int &v=ch[x][A.s[i]];
        if(!v) v=++nod;x=v;
    }
    tag[x].pb(id);
}
void dfs(int x)
{
    for(int l=tag[x].size(),i=0;i<l;i++)
        rk[++tt]=tag[x][i];
    if(ch[x][0]) dfs(ch[x][0]);
    if(ch[x][1]) dfs(ch[x][1]);
}
int main()
{
    cin>>n>>m>>q;bit[1]=1;
    for(int i=2;i<=n;i++) bit[i]=2ll*bit[i-1]%mod;
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=1;i<=m;i++)
        for(int j=n,p=0;j>=1;j--)
            A[i].s[++p]=s[j][i]-'0';
    for(int i=1;i<=m;i++) Insert(A[i],i);
    dfs(1);
    for(int i=1;i<=m;i++) A[rk[i]].rk=i;
    for(int i=1;i<m;i++) d[i]=A[rk[i+1]]-A[rk[i]];
    for(int i=1;i<=n;i++) A[m+1].s[i]=0,A[m+2].s[i]=1;
    d[0]=A[rk[1]]-A[m+1],d[m]=(A[m+2]-A[rk[m]]+1)%mod;
    for(int w=1;w<=q;w++)
    {
        scanf("%s",s[0]+1);
        int ans=0,mxl=0,mnr=m+1;
        for(int i=1;i<=m;i++)
            if(s[0][i]=='1') mnr=min(mnr,A[i].rk);
            else mxl=max(mxl,A[i].rk);
        if(mxl<mnr) ans=d[mxl];
        printf("%d\n",ans);
    }
    return 0;
}

D1T2 轉盤

題意

一次小 G 和小 H 本來準備去聚餐,但因爲太麻煩了因而題面簡化以下:

一個轉盤上有擺成一圈的 \(n\) 個物品(編號 \(1\)\(n\))其中第 \(i\) 個物品會在 \(T_i\) 時刻出現。

\(0\) 時刻時,小 G 能夠任選 \(n\) 個物品中的一個,咱們將其編號記爲 \(s_0\)。而且若是 \(i\) 時刻選擇了物品 \(s_i\),那麼 \(i + 1\) 時刻能夠繼續選擇當前物品或者選擇下一個物品。當 \(s_i\)\(n\) 時,下一個物品爲物品 \(1\),不然下一個物品爲 \(s_{i} + 1\)。在每一時刻(包括 \(0\) 時刻),若是小 G 所選擇的物品已經出現了,那麼小 G 將會標記它。小 H 想知道,在物品選擇的最優策略下,小 G 何時能標記全部物品?

但麻煩的是,物品的出現時間會不時修改。咱們將其描述爲 \(m\) 次修改,每次修改將改變其中一個物品的出現時間。每次修改以後,你也須要求出當前局面的答案。對於其中部分測試點,小 H 還追加了強制在線的要求。

測試點編號 \(n\) \(m\) \(T_i/T_x\) \(p\)
1 \(\le 10\) \(\le 10\) \(\le 10\) \(=0\)
2 \(\le 1000\) \(=0\) \(\le 1000\) \(=0\)
3 \(\le 10^5\) \(=0\) \(\le 10^5\) \(=0\)
4 \(\le 5000\) \(\le 5000\) \(\le 10^5\) \(=0\)
5 \(\le 8\times 10^4\) \(\le 8\times 10^4\) \(\le 10^5\) \(=0\)
6 \(\le 8\times 10^4\) \(\le 8\times 10^4\) \(\le 10^5\) \(=1\)
7 \(\le 9\times 10^4\) \(\le 9\times 10^4\) \(\le 10^5\) \(=0\)
8 \(\le 9\times 10^4\) \(\le 9\times 10^4\) \(\le 10^5\) \(=1\)
9 \(\le 10^5\) \(\le 10^5\) \(\le 10^5\) \(=0\)
10 \(\le 10^5\) \(\le 10^5\) \(\le 10^5\) \(=1\)

題解

我真佩服去年的本身、居然有40分,而今天看了很久纔看懂去年的作法。

首先能夠證實的是必定是隻走一圈。

去年的40分作法:

序列倍長後,\(a[i]=T[i]-i\),對於a維護單調遞減隊列,答案爲n個滑動窗口的隊頭+i。能夠把a當作是等待時間,最後加上n-1就是真正的答案了。

AC作法:

其實答案求的就是\[min_{i=1}^{n}[max_{j=i}^{i+n}A_j+i]\]

發現\(A_j>A_{j+n}\)後,式子裏的max就能夠換成後綴max了。考慮用線段樹維護這個東西。

每一個節點\((l,r)\)維護\(mx[x]\)表示最大的A,\(ans[x]\)表示\(i\)取到\([l,mid]\)時候的最小答案。

合併信息就從新遞歸一下,和男神那題超級像。

複雜度\(\mathcal O(nlog^2n)\)

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int N=2e5+10;
int n,m,op,T[N],a[N],ans[N<<2],mx[N<<2],Ans;
int calc(int x,int l,int r,int b)
{
    if(l==r) return l+max(mx[x],b);
    int mid=(l+r)>>1;
    if(mx[x<<1|1]>=b) return min(ans[x],calc(x<<1|1,mid+1,r,b));
    else return min(calc(x<<1,l,mid,b),mid+1+b);
}
void pushup(int x,int l,int r)
{
    mx[x]=max(mx[x<<1],mx[x<<1|1]);
    ans[x]=calc(x<<1,l,(l+r)>>1,mx[x<<1|1]);
}
void build(int x,int l,int r)
{
    if(l==r) {mx[x]=a[l],ans[x]=T[l];return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x,l,r);
}
void update(int x,int l,int r,int p)
{
    if(l==r) {mx[x]=a[l],ans[x]=T[l];return;}
    int mid=(l+r)>>1;
    if(p<=mid) update(x<<1,l,mid,p);
    else update(x<<1|1,mid+1,r,p);
    pushup(x,l,r);
}
int main()
{
    cin>>n>>m>>op;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&T[i]);a[i]=T[i]-i;
        T[i+n]=T[i],a[i+n]=T[i]-(i+n);
    }
    build(1,1,n*2);printf("%d\n",Ans=ans[1]+n-1);
    for(int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        if(op) x^=Ans,y^=Ans;
        T[x]=T[x+n]=y,a[x]=y-x,a[x+n]=y-x-n;
        update(1,1,n*2,x),update(1,1,n*2,x+n);
        printf("%d\n",Ans=ans[1]+n-1);
    }
    return 0;
}

D1T3 毒瘤

題意

從前有一名毒瘤。

毒瘤最近發現了量產毒瘤題的奧祕。考慮以下類型的數據結構題:給出一個數組,要求支持若干種奇奇怪怪的修改操做(例如給一個區間內的數同時加上 \(c\),或者將一個區間內的數同時開平方根),而且支持詢問區間的和。毒瘤考慮了 \(n\) 個這樣的修改操做,並將它們編號爲 \(1 \ldots n\)。當毒瘤要出數據結構題的時候,他就將這些修改操做中選若干個出來,而後出成一道題。

固然了,這樣出的題有可能不可作。經過精妙的數學推理,毒瘤揭露了這些修改操做之間的關係:有 \(m\) 對「互相排斥」的修改操做,第 \(i\) 對是第 \(u_i\) 個操做和第 \(v_i\) 個操做。當一道題中同時含有 \(u_i\)\(v_i\) 這兩個操做時,這道題就會變得不可作。另外一方面,當一道題中不包含任何「互相排斥」的操做時,這個題就是可作的。此外,毒瘤還發現了一個規律:\(m − n\) 是一個很小的數字(參見「數據範圍」中的說明),且任意兩個修改操做都是連通的。兩個修改操做 \(a, b\) 是連通的,當且僅當存在若干操做 \(t_0, t_1, ... , t_l\),使得 \(t_0 = a,t_l = b\),且對任意 \(1 \le i \le l\)\(t_{i−1}\)\(t_i\) 都是「互相排斥」的修改操做。

一對「互相排斥」的修改操做稱爲互斥對。如今毒瘤想知道,給定值 \(n\)\(m\) 個互斥對,他一共能出出多少道可作的不一樣的數據結構題。兩個數據結構題是不一樣的,當且僅當其中某個操做出如今了其中一個題中,可是沒有出如今另外一個題中。

測試點 # 1~4 5~6 7~8 9 10~11 12~14 15~16 17~20
\(n \le\) \(20\) \(10^5\) \(10^5\) \(3000\) \(10^5\) \(3000\) \(10^5\) \(10^5\)
\(m \le\) \(n + 10\) \(n - 1\) \(n\) \(n + 1\) \(n + 1\) \(n + 10\) \(n + 7\) \(n + 10\)

題解

就是求有11條返祖邊的樹的獨立集個數。

很良心地給了75左右的暴力容斥部分分。

正解:

把11*2個點摳出來建虛樹,一共不到50個點。預處理出沒有返祖邊的樹的dp值。

如今考慮仍然暴力容斥,可是計算過程能夠只用在虛樹上計算,也就是說優化掉一個\(n\)

發現虛樹上每條邊的轉移係數是必定的,把未知數代進去轉移、就能夠預處理出轉移係數了。

具體來講個人\(g[x][0/1]=(a,b)\)\(x\)的虛樹父親爲\(f\),則
\[ dp[f][0]*=(g[x][0].a*dp[x][0]+g[x][1].a*dp[x][1]); \]

\[ dp[f][1]*=(g[x][0].b*dp[x][0]+g[x][1].b*dp[x][1]); \]

因此顯然初值\(g[x][0]=(1,1),g[x][1]=(1,0)\)

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
#define pa pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
const int N=2e5+10,mod=998244353;
struct edge{int next,to;}a[N];
int head[N],cnt,n,m,sf[N],fa[N],f[N][2],ST[N][18];
int dfn[N],S[N],c,tot,dep[N],sta[N],top,out[N];
void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;}
int find(int x) {return sf[x]==x?x:sf[x]=find(sf[x]);}
int ksm(int x,int k)
{
    int s=1;for(;k;k>>=1,x=1ll*x*x%mod)
                if(k&1) s=1ll*s*x%mod;return s; 
}
void dfs(int x,int fr)
{
    fa[x]=fr;ST[x][0]=fr;
    dep[x]=dep[fr]+1;dfn[x]=++tot;
    for(int p=1;p<=16;p++)
        ST[x][p]=ST[ST[x][p-1]][p-1];
    f[x][0]=f[x][1]=1;
    for(int i=head[x];i;i=a[i].next)
    {
        int R=a[i].to;if(R==fr) continue;
        dfs(R,x);
        f[x][0]=1ll*f[x][0]*(f[R][0]+f[R][1])%mod;
        f[x][1]=1ll*f[x][1]*f[R][0]%mod;
    }
    out[x]=tot;
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int p=16;p>=0;p--)
        if(dep[ST[x][p]]>=dep[y]) x=ST[x][p];
    for(int p=16;p>=0;p--)
        if(ST[x][p]!=ST[y][p])
            x=ST[x][p],y=ST[y][p];
    return x==y?x:ST[x][0];
}
void Jump(int &R,int x)
{
    for(int p=16;p>=0;p--)
        if(dep[ST[R][p]]>dep[x]) R=ST[R][p];
}
int cmp(int a,int b) {return dfn[a]<dfn[b];}

vector<int> E[N];
int ban[N],g[N][2],s,Ans;
pa f0[N],f1[N],M[N];
void Calc(int x,int y)
{
    int p=x;
    f0[x]=mp(1,1);f1[x]=mp(1,0);
    while(fa[p]!=y)
    {
        int bs0=1,bs1=1;
        for(int i=head[fa[p]];i;i=a[i].next)
        {
            int R=a[i].to;
            if(R==fa[fa[p]]||R==p) continue;
            bs0=1ll*bs0*(f[R][0]+f[R][1])%mod;
            bs1=1ll*bs1*f[R][0]%mod;
        }
        pa ff0=mp(1ll*f0[x].fi*bs0%mod,1ll*f0[x].se*bs0%mod);
        pa ff1=mp(1ll*f1[x].fi*bs1%mod,1ll*f1[x].se*bs1%mod);
        f0[x]=mp((ff0.fi+ff1.fi)%mod,(ff0.se+ff1.se)%mod);
        f1[x]=mp(ff0.fi,ff0.se);
        p=fa[p];
    }
}
int DP()
{
    for(int i=1;i<=c;i++) g[S[i]][1]=1,g[S[i]][0]=ban[S[i]]?0:1;
    for(int i=c;i>=1;i--)
    {
        int x=S[i];
        g[x][0]*=f[x][0],g[x][1]*=f[x][1];
        for(int j=0,l=E[x].size();j<l;j++)
        {
            int R=E[x][j];Jump(R,x);
            g[x][0]=1ll*g[x][0]*ksm((f[R][0]+f[R][1])%mod,mod-2)%mod;
            g[x][1]=1ll*g[x][1]*ksm(f[R][0],mod-2)%mod;
        }
        for(int j=0,l=E[x].size();j<l;j++)
        {
            int R=E[x][j];
            g[x][0]=1ll*g[x][0]*(1ll*f0[R].fi*g[R][0]%mod+1ll*f0[R].se*g[R][1]%mod)%mod;
            g[x][1]=1ll*g[x][1]*(1ll*f1[R].fi*g[R][0]%mod+1ll*f1[R].se*g[R][1]%mod)%mod;
        }
    }
    return (g[1][0]+g[1][1])%mod;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) sf[i]=i;
    for(int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        if(find(x)!=find(y)) sf[find(x)]=find(y),link(x,y),link(y,x);
        else M[++s]=mp(x,y),S[++c]=x,S[++c]=y;
    }
    dfs(1,0);
    sort(S+1,S+c+1,cmp);
    for(int i=2,t=c;i<=t;i++) S[++c]=LCA(S[i-1],S[i]);
    S[++c]=1;sort(S+1,S+c+1,cmp);
    c=unique(S+1,S+c+1)-S-1;
    for(int i=1;i<=c;sta[++top]=S[i],i++)
    {
        while(top&&dfn[S[i]]>out[sta[top]]) top--;
        if(top) E[sta[top]].pb(S[i]),Calc(S[i],sta[top]);
    }

    for(int zt=0,d=1;zt<1<<s;zt++)
    {
        for(int i=1;i<=s;i++)
            if(zt&(1<<(i-1)))
                d=mod-d,ban[M[i].fi]=ban[M[i].se]=1;
        int res=DP();
        (Ans+=1ll*res*d%mod)%=mod;
        d=1;for(int i=1;i<=s;i++)
                ban[M[i].fi]=ban[M[i].se]=0;
    }
    cout<<Ans<<endl;
}

D2T1 遊戲

題意

一次小 G 和小 H 在玩尋寶遊戲,有 \(n\) 個房間排成一列,編號爲 \(1,2,…,n\),相鄰房間之間都有 \(1\) 道門。其中一部分門上有鎖(所以須要對應的鑰匙才能開門),其他的門都能直接打開。

如今小 G 告訴了小 H 每把鎖的鑰匙在哪一個房間裏(每把鎖有且只有一把鑰匙),並做出 \(p\) 次指示:第 \(i\) 次讓小 H 從第 \(S_i\) 個房間出發,去第 \(T_i\) 個房間尋寶。可是小 G 有時會故意在指令裏放入死路,而小 H 也不想浪費多餘的體力去嘗試,因而想事先調查清楚每次的指令是否存在一條通路。

你是否能爲小 H 做出解答呢?

測試點編號 n m 其餘特性
1 $ \le 1000 $ $ \le 1000 $
2 $ \le 1000 $ $ \le 1000 $
3 $ \le 10^5 $ $ \le 10^5 $ \(y \le x\) 恆成立
4 $ \le 10^5 $ $ \le 10^5 $ \(y \le x\) 恆成立
5 $ \le 10^5 $ $ \le 10^5 $
6 $ \le 10^5 $ $ \le 10^5 $
7 $ \le 10^6 $ $ \le 10^6 $ \(y \le x\) 恆成立
8 $ \le 10^6 $ $ \le 10^6 $ \(y \le x\) 恆成立
9 $ \le 10^6 $ $ \le 10^6 $
10 $ \le 10^6 $ $ \le 10^6 $

對於全部數據,保證 \(1 \le n,p \le 10^6\)\(0 \le m < n\)\(1 \le x, y, S_i,T_i < n\),保證 \(x\) 不重複。

因爲本題輸入文件較大,建議在程序中使用讀入優化。

題解

被暴力艹過90分真的無語。省選若是出現這種狀況退役那也沒有什麼辦法了。

方法是對於每一個點維護向左以及向右最多能到的區間。

先用單調棧維護最大可能區間,以後由\([l,r]\)擴展,找到\([LS,l-1]\)中從右往左第一個\(key[i]>r\)的地方,並把\(l\)設置爲\(i+1\)。以後即可以拓展右區間。

因爲右區間的擴展相似於單調棧,因此不難發現拓展次數最多爲\(\mathcal O(n)\)。所以總複雜度爲\(\mathcal O(nlogn)\)

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int N=1e6+10;
int n,m,q,key[N],l[N],r[N],t[N<<2];
int sta[N],top,LS[N],RS[N];
void build(int x,int l,int r)
{
    if(l==r) {t[x]=key[l];return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    t[x]=max(t[x<<1],t[x<<1|1]);
}
int query(int x,int l,int r,int gl,int gr,int bs)
{
    int mid=(l+r)>>1,res=0;
    if(l>=gl&&r<=gr)
    {
        if(t[x]<=bs) return 0;
        if(l==r) return l;
        if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs);
        else return query(x<<1,l,mid,gl,gr,bs);
    }
    if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs);
    if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs);
    return res;
}
int Find(int l,int r,int x)
{
    if(l>r) return l;
    int p=query(1,1,n,l,r,x);
    return p?p+1:l;
}
int main()
{
    cin>>n>>m>>q;
    for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y;
    build(1,1,n);LS[1]=1,RS[n]=n;
    for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1];
    for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1];
    for(int i=n;i>=1;i--)
    {
        r[i]=i;l[i]=Find(LS[i],i-1,i);
        while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i])))
            r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]);
    }
    for(int i=1,x,y;i<=q;i++)
        scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO");
}

D2T2 排列

題意

給定 \(n\) 個整數 \(a_1, a_2, \ldots , a_n(0 \le a_i \le n)\),以及 \(n\) 個整數 \(w_1, w_2, …, w_n\)。稱 \(a_1, a_2, \ldots , a_n\) 的一個排列 \(a_{p[1]}, a_{p[2]}, \ldots , a_{p[n]}\)\(a_1, a_2, \ldots , a_n\) 的一個合法排列,當且僅當該排列知足:對於任意的 \(k\) 和任意的 \(j\),若是 \(j \le k\),那麼 \(a_{p[j]}\) 不等於 \(p[k]\)。(換句話說就是:對於任意的 \(k\) 和任意的 \(j\),若是 \(p[k]\) 等於 \(a_{p[j]}\),那麼 \(k<j\)。)

定義這個合法排列的權值爲 \(w_{p[1]} + 2w_{p[2]} + \ldots + nw_{p[n]}\)。你須要求出在全部合法排列中的最大權值。若是不存在合法排列,輸出 \(-1\)

樣例解釋中給出了合法排列和非法排列的實例。

對於前 \(20\%\) 的數據,\(1 \le n \le 10\)

對於前 \(40\%\) 的數據,\(1 \le n \le 15\)

對於前 \(60\%\) 的數據,\(1 \le n \le 1000\)

對於前 \(80\%\) 的數據,\(1 \le n \le 100000\)

對於 \(100\%\) 的數據,\(1 \le n \le 500000\)\(0 \le a_i \le n (1 \le i \le n)\)\(1 \le w_i \le 10^9\) ,全部 \(w_i\) 的和不超過 \(1.5 \times 10^{13}\)

題解

這題的映射關係很是複雜好嘛!但願不要出現這種題目特別難懂的題目了!

把這題映射關係搞清楚後,發現就是\(a[i]->i\),而後在這棵樹(有環無解)上按照拓撲序依次選完全部的點,貢獻爲選某點的時間×該點權值。

這樣大概有40分的狀壓DP,可是考慮正解:

顯然權值小的點要先選,那麼權值最小的點在選完其父親(若是有的話)後,必定立刻被選。
考慮每一個點向父親縮,代價爲父親的siz×該點的val。因而各個聯通塊的權值如何肯定呢?

考慮兩個聯通塊AB,當前時刻爲i,能夠很輕鬆地列出\(W_{AB},W_{BA}\)的式子,相減發現\(\frac{\sum val}{siz}\)小的被先選會更優。

因此用一個set維護每一個點,每次選取最小點向父親合併,最後合成一個點就行了。

這題據說是YALI考過的題,HNOI考完走出考場聽到許多「YALI人AK了」之類的言語,不是很爽快——HNOI有YALI學長出的題。固然不能否認的是YALI確實很強,應該也沒有泄題的狀況。可是我總以爲在這種大賽搬原題是一種極其不負責任的表現。

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int N=1e6+10;
int n,m,q,key[N],l[N],r[N],t[N<<2];
int sta[N],top,LS[N],RS[N];
void build(int x,int l,int r)
{
    if(l==r) {t[x]=key[l];return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    t[x]=max(t[x<<1],t[x<<1|1]);
}
int query(int x,int l,int r,int gl,int gr,int bs)
{
    int mid=(l+r)>>1,res=0;
    if(l>=gl&&r<=gr)
    {
        if(t[x]<=bs) return 0;
        if(l==r) return l;
        if(t[x<<1|1]>bs) return query(x<<1|1,mid+1,r,gl,gr,bs);
        else return query(x<<1,l,mid,gl,gr,bs);
    }
    if(gr>mid) res=query(x<<1|1,mid+1,r,gl,gr,bs);
    if(gl<=mid&&!res) res=query(x<<1,l,mid,gl,gr,bs);
    return res;
}
int Find(int l,int r,int x)
{
    if(l>r) return l;
    int p=query(1,1,n,l,r,x);
    return p?p+1:l;
}
int main()
{
    cin>>n>>m>>q;
    for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),key[x]=y;
    build(1,1,n);LS[1]=1,RS[n]=n;
    for(int i=2;i<=n;i++) LS[i]=(key[i-1]&&key[i-1]<=i-1)?i:LS[i-1];
    for(int i=n-1;i>=1;i--) RS[i]=(key[i]&&key[i]>i)?i:RS[i+1];
    for(int i=n;i>=1;i--)
    {
        r[i]=i;l[i]=Find(LS[i],i-1,i);
        while(r[i]<n&&(!key[r[i]]||(key[r[i]]>=l[i]&&key[r[i]]<=r[i])))
            r[i]=r[r[i]+1],l[i]=Find(LS[i],l[i]-1,r[i]);
    }
    for(int i=1,x,y;i<=q;i++)
        scanf("%d%d",&x,&y),puts((l[x]<=y&&r[x]>=y)?"YES":"NO");
}

D2T3 道路

題意

W 國的交通呈一棵樹的形狀。W 國一共有 \(n − 1\) 個城市和 \(n\) 個鄉村,其中城市從 \(1\)\(n − 1\) 編號,鄉村從 \(1\)\(n\) 編號,且 \(1\) 號城市是首都。道路都是單向的,本題中咱們只考慮從鄉村通往首都的道路網絡。對於每個城市,恰有一條公路和一條鐵路通向這座城市。對於城市 \(i\),通向該城市的道路(公路或鐵路)的起點,要麼是一個鄉村,要麼是一個編號比 \(i\) 大的城市。沒有道路通向任何鄉村。除了首都之外,從任何城市或鄉村出發只有一條道路;首都沒有往外的道路。從任何鄉村出發,沿着惟一往外的道路走,總能夠到達首都。

W 國的國王小 W 得到了一筆資金,他決定用這筆資金來改善交通。因爲資金有限,小 W 只能翻修 \(n − 1\) 條道路。小 W 決定對每一個城市翻修剛好一條通向它的道路,即從公路和鐵路中選擇一條並進行翻修。小 W 但願從鄉村通向城市能夠儘量地便利,因而根據人口調查的數據,小 W 對每一個鄉村制定了三個參數,編號爲 \(i\) 的鄉村的三個參數是 \(a_i\)\(b_i\)\(c_i\)。假設從編號爲 \(i\) 的鄉村走到首都一共須要通過 \(x\) 條未翻修的公路與 \(y\) 條未翻修的鐵路,那麼該鄉村的不便利值爲
\[ c_i \cdot (ai + x) \cdot (bi + y) \]
在給定的翻修方案下,每一個鄉村的不便利值相加的和爲該翻修方案的不便利值。

翻修 \(n − 1\) 條道路有不少方案,其中不便利值最小的方案稱爲最優翻修方案,小 W 天然但願找到最優翻修方案,請你幫助他求出這個最優翻修方案的不便利值。

\(20\) 組數據,編號爲 \(1 ∼ 20\)

對於編號 \(\le 4\) 的數據,\(n \le 20\)

對於編號爲 \(5 \sim 8\) 的數據,\(a_i, b_i, c_i \le 5,n \le 50\)

對於編號爲 \(9 \sim 12\) 的數據,\(n \le 2000\)

對於全部的數據,\(n \le 20000\)\(1 \le a_i, b_i \le 60\)\(1 \le c_i \le 10^9\)\(s_i, t_i\)\([−n, −1] \cap (i, n − 1]\) 內的整數,任意鄉村能夠經過不超過 \(40\) 條道路到達首都。

題解

聽說這題出題人想複雜了因而成爲了普及題。。。驗題人幹嗎去了啊。。

然而我剛纔苦苦思索十分鐘仍是忘記怎麼作了(去年作的)。。就怕被降智啊!!!

\(dp[x][a][b]\)表示\(x\)的子樹內,到根還有a條沒有修好的公路、b條沒有修好的鐵路的最小總代價。

沒了。

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int read()
{
    char ch=getchar();int h=0,t=1;
    while(ch!='-'&&(ch>'9'||ch<'0'))ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch>='0'&&ch<='9'){h=h*10+ch-'0';ch=getchar();}
    return h*t; 
}
const int MAXN=40010;
int N,head[MAXN],cnt,L[MAXN],R[MAXN];
int A[MAXN],B[MAXN],C[MAXN];
ll dp[MAXN>>1][41][41];
struct edge{int next,to,w;}a[MAXN<<2];
void link(int x,int y,int w){a[++cnt]=(edge){head[x],y,w};head[x]=cnt;}
void Pre(int x,int fa)
{
    for(int i=head[x];~i;i=a[i].next)
    {
        int S=a[i].to;
        if(S==fa)continue;
        L[S]=L[x],R[S]=R[x];
        (a[i].w==1)?L[S]++:R[S]++;
        Pre(S,x);
    }
}
ll DP(int x,int i,int j)
{
    if(x<=N) return dp[x][i][j];
    return 1LL*C[x]*(A[x]+i)*(B[x]+j);
}
void DFS(int x,int fa)
{
    int lc=0,rc=0;
    for(int i=head[x];~i;i=a[i].next)
        if(a[i].to!=fa){DFS(a[i].to,x);rc?lc=a[i].to:rc=a[i].to;}
    if(!lc) return;
    for(int i=0;i<=L[x];i++)
        for(int j=0;j<=R[x];j++)
            dp[x][i][j]=min(DP(lc,i+1,j)+DP(rc,i,j),DP(lc,i,j)+DP(rc,i,j+1));
}
int main()
{
    N=read();
    memset(head,-1,sizeof(head));
    for(int i=1;i<N;i++)
    {
        int x=read(),y=read();
        if(x<0)x=-x+N;
        if(y<0)y=-y+N;
        link(i,x,1);link(x,i,1);
        link(i,y,2);link(y,i,2);
    }
    for(int i=1;i<=N;i++)
    {
        int pos=i+N;
        A[pos]=read();
        B[pos]=read();
        C[pos]=read();
    }
    Pre(1,0);
    DFS(1,0);
    printf("%lld\n",dp[1][0][0]);
    return 0;
}

後記

這套題目能夠說是很是好、質量很是高的啦。

若是今年讓我考這套題目的話,最好的成績是30+40+75+60+40+100,然而算上聯賽也只能踩隊線。
能夠說很是刺激了。

後天加油啊!

相關文章
相關標籤/搜索