2019 Multi-University Training Contest 6

A. Salty Fish算法

假設要偷走全部的蘋果,那麼如今須要放棄一些蘋果,而且黑掉一些監控相機以保證能偷走沒有放棄的蘋果,咱們須要最小化放棄的蘋果的收益之和加上黑掉相機支付的代價的總和。
數組

考慮最小割建圖:
函數

  • 源點$S$向每一個監控相機連邊,割掉這條邊的代價爲黑掉它的代價,割掉這條邊表示黑掉這個監控相機。
  • 每一個節點向匯點$T$連邊,割掉這條邊的代價爲這個點的蘋果數,割掉這條邊表示放棄這個節點的蘋果。
  • 每一個監控相機向其監控範圍內的全部節點連邊,割掉這條邊的代價爲$+\infty$,表示不能破壞監控關係。

那麼每條$S$到$T$的路徑都表示某個節點既沒有並放棄,又被一些相機監控着,這是不合法的。咱們須要割掉代價之和最少的邊,使得$S$和$T$不連通,所以最終的答案就是全部節點的蘋果數量之和減去這個圖的最小割。ui

由於最小割=最大流,從葉子向根節點依次考慮每棵子樹,計算最大流。設$v[i][j]$表示$i$的子樹中離$i$距離爲$j$的那些節點還能提供多少流量,那麼考慮監控範圍最高點在$i$的每一個監控相機,顯然應該優先接收距離較大的那些節點的流量。spa

用std::map存儲每一個值非零的$v[i]$,能夠在總計$O(m\log n)$的時間內求出最大流。至於$v[i]$的計算,能夠由$i$的兒子的$v$啓發式合併而來。blog

注意到這是關於深度的一個啓發式合併,若是將這棵樹長鏈剖分,計算出每一個點$x$子樹內離$x$距離最遠的點到$x$的距離$d[x]$,那麼能夠選擇將$d$最大的兒子的$v$繼承給$x$,而後將其它兒子的$v$暴力插入到$v[x]$中。由於每一個點僅屬於一條長鏈,且一條長鏈只會在鏈頂位置做爲短兒子被暴力合併一次,因此合併的時間複雜度爲$O(n)$。排序

總時間複雜度爲$O((n+m)\log n)$。繼承

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=300010;
int Case,n,m,i,j,x,A,B,f[N],d[N],a[N],e[N][2],g[N],nxt[N];ll ans;map<int,ll>T[N];
inline void add(int x,int y){nxt[y]=g[x];g[x]=y;}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&m);
    ans=0;
    for(i=1;i<=n;i++)g[i]=0,T[i].clear();
    for(i=2;i<=n;i++)scanf("%d",&f[i]),d[i]=d[f[i]]+1;
    for(i=1;i<=n;i++)scanf("%d",&a[i]),ans+=a[i];
    for(i=1;i<=m;i++)scanf("%d%d%d",&x,&e[i][0],&e[i][1]),add(x,i);
    for(i=n;i;i--){
      T[i][d[i]]+=a[i];
      for(j=g[i];j;j=nxt[j]){
        A=d[i]+e[j][0],B=e[j][1];
        while(B&&T[i].size()){
          map<int,ll>::iterator it=T[i].upper_bound(A);
          if(it==T[i].begin())break;
          it--;
          x=B<it->second?B:it->second;
          B-=x;
          it->second-=x;
          ans-=x;
          if(!it->second)T[i].erase(it);
        }
      }
      if(i>1){
        x=f[i];
        if(T[x].size()<T[i].size())swap(T[x],T[i]);
        for(map<int,ll>::iterator it=T[i].begin();it!=T[i].end();it++)if(it->second)T[x][it->first]+=it->second;
      }
    }
    printf("%lld\n",ans);
  }
}

  

B. Nonsense Timerem

考慮時間倒流,看做一個完整的排列按照必定順序依次刪除每一個數,而後每次須要計算LIS的長度。it

首先在$O(n\log n)$的時間內求出LIS,並找到一個LIS。當刪除$x$時,若是$x$不在以前找到的那個LIS中,那麼顯然LIS的長度是不會變化的,不然暴力從新計算出新的LIS便可。

由於數據隨機,所以LIS的指望長度是$O(\sqrt{n})$,刪除的$x$位於LIS中的機率是$\frac{1}{\sqrt{n}}$,也就是說指望刪除$O(\sqrt{n})$個數纔會修改LIS,那麼LIS變化的次數不會不少。

指望時間複雜度爲$O(n\sqrt{n}\log n)$。

#include<cstdio>
const int N=50010;
int Case,n,i,x,a[N],b[N],ans[N],pre[N],nxt[N],f[N],g[N],used[N],bit[N];
inline void up(int&a,int b){if(f[a]<f[b])a=b;}
inline void build(){
  int i,j,k;
  for(i=nxt[0];i<=n+1;i=nxt[i]){
    used[i]=0;
    k=0;
    for(j=a[i];j;j-=j&-j)up(k,bit[j]);
    f[i]=f[k]+1;
    g[i]=k;
    for(j=a[i];j<=n+2;j+=j&-j)up(bit[j],i);
  }
  for(i=nxt[0];i<=n+1;i=nxt[i])for(j=a[i];j<=n+2;j+=j&-j)bit[j]=0;
  for(i=n+1;i;i=g[i])used[i]=1;
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i]++;
    a[0]=1;
    a[n+1]=n+2;
    for(i=0;i<=n+1;i++)pre[i]=i-1,nxt[i]=i+1,bit[i]=used[i]=0;
    bit[n+2]=0;
    for(i=1;i<=n;i++)scanf("%d",&b[i]);
    build();
    for(i=n;i;i--){
      ans[i]=f[n+1]-1;
      x=b[i];
      pre[nxt[x]]=pre[x];
      nxt[pre[x]]=nxt[x];
      if(used[x])build();
    }
    for(i=1;i<=n;i++)printf("%d%c",ans[i],i<n?' ':'\n');
  }
}

  

C. Milk Candy

創建一張$n+1$個點的圖,點的編號爲$0$到$n$,點$i$表示$s_i=x_1+x_2+\dots+x_i$。若是咱們知道了$x_l+x_{l+1}+\dots+x_r$,那麼咱們就知道了$s_r-s_{l-1}$ 的值,在$l-1$和$r$之間連一條邊。若是這個圖是連通的,那麼咱們就能根據$s_0=0$推出全部$s$,從而推出全部$x$。問題轉化爲從每一個NPC手中剛好購買$k_i$條邊,使得這個圖連通,且代價之和最小。

從另一個角度考慮這個問題:先購買全部邊,而後從每一個NPC手中刪除不超過$c_i-k_i$條邊,總計刪除剛好$\sum(c_i-k_i)$條邊,使得剩下的圖仍然連通,且刪去的邊代價之和最大。因爲刪去邊後圖連通等價於剩下的邊存在生成樹,生成樹是圖擬陣的基,因此這是圖擬陣的對偶擬陣$M_1$;而從每一個邊集中選擇不超過若干條邊的條件,則是劃分擬陣$M_2$。

因此咱們的目標就是找到這兩個擬陣的交的大小爲$\sum(c_i-k_i)$的權值和最大的獨立集,能夠用擬陣交算法解決:

  • 令初始解$I$爲空集,即沒有邊被刪除。
  • 每條邊做爲有向圖中的一個點,並新建源點$S$和匯點$T$。
  • 對於$x\notin I$的某條邊$x$,將$x$的點權設置爲$w_x$,表示額外刪掉這條邊的代價。若$I\cup x$知足$M_1$,則連邊$S\rightarrow x$;若$I\cup x$知足$M_2$,則連邊$x\rightarrow T$。
  • 對於$x\in I$的某條邊$x$,將$x$的點權設置爲$-w_x$,表示取消刪除這條邊的代價。
  • 對於$x\in I$的某條邊$x$以及$y\notin I$的某條邊$y$,若$I\setminus x\cup y$知足$M_1$,則連邊$x\rightarrow y$;若$I\setminus x\cup y$知足$M_2$,則連邊$y\rightarrow x$。
  • 在構造出來的圖中SPFA找到$S$到$T$的最長路做爲增廣路,將上面每條邊的選擇狀況取反,獲得新的解$I'$,此時$I'$的大小比$I$恰好大$1$。不斷重複構圖找增廣路直至$I$的大小爲$\sum(c_i-k_i)$。

不妨認爲$n,m,\sum c$同階,則一共$O(n)$次增廣,每次增廣建圖須要$O(n^3)$的時間,尋找增廣路須要$O(n^3)$的SPFA,總時間複雜度爲$O(n^4)$。

#include<cstdio>
const int N=85,M=100000,inf=~0U>>1;
int Case,n,cnt,m,goal,have,num[N],lim[N],i,j;
int S,T,x,y,g[N],v[N<<1],nxt[N<<1],ed,vis[N];
int cost[N],col[N],use[N],ans;
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
void dfs(int x){
  if(vis[x])return;
  vis[x]=1;
  for(int i=g[x];i;i=nxt[i])if(use[i>>1])dfs(v[i]);
}
inline bool check(){
  int i;
  for(i=0;i<=n;i++)vis[i]=0;
  dfs(0);
  for(i=0;i<=n;i++)if(!vis[i])return 0;
  return 1;
}
inline bool check2(){
  for(int i=1;i<=cnt;i++)if(num[i]<lim[i])return 0;
  return 1;
}
namespace Matroid{
int g[N],v[M],nxt[M],ed,q[M],h,t,d[N],pre[N],w[N];bool in[N];
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
inline void ext(int x,int y,int z){
  if(d[x]<=y)return;
  d[x]=y;
  pre[x]=z;
  if(in[x])return;
  q[++t]=x;
  in[x]=1;
}
inline bool find(){
  int i,j;
  S=m+1,T=m+2;
  for(ed=0,i=1;i<=T;i++)g[i]=0;
  for(i=1;i<=m;i++)if(use[i]){
    w[i]=-cost[i];
    use[i]^=1;
    num[col[i]]--;
    if(check())add(S,i);
    if(check2())add(i,T);
    num[col[i]]++;
    use[i]^=1;
  }else w[i]=cost[i];
  for(i=1;i<=m;i++)if(use[i])for(j=1;j<=m;j++)if(!use[j]){
    use[i]^=1,use[j]^=1;
    num[col[i]]--;num[col[j]]++;
    if(check())add(j,i);
    if(check2())add(i,j);
    num[col[i]]++;num[col[j]]--;
    use[i]^=1,use[j]^=1;
  }
  for(i=1;i<=T;i++)d[i]=inf,in[i]=0;
  q[h=t=1]=S;
  d[S]=0,in[S]=1;
  while(h<=t){
    x=q[h++];
    for(i=g[x];i;i=nxt[i])ext(v[i],d[x]+w[v[i]],x);
    in[x]=0;
  }
  if(d[T]==inf)return 0;
  ans+=d[T];
  while(pre[T]!=S){
    T=pre[T];
    if(use[T])num[col[T]]--;else num[col[T]]++;
    use[T]^=1;
  }
  return 1;
}
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&cnt);
    m=goal=ans=0;
    for(i=0;i<=n;i++)g[i]=0;
    for(ed=i=1;i<=cnt;i++){
      scanf("%d%d",&num[i],&lim[i]);
      goal+=lim[i];
      for(j=0;j<num[i];j++){
        m++;
        col[m]=i;
        scanf("%d%d%d",&x,&y,&cost[m]);
        add(x-1,y);
        add(y,x-1);
        use[m]=1;
        ans+=cost[m];
      }
    }
    if(!check()){
      puts("-1");
      continue;
    }
    have=m;
    while(have>goal){
      if(!Matroid::find())break;
      have--;
    }
    if(have!=goal)ans=-1;
    printf("%d\n",ans);
  }
}

  

D. Speed Dog

問題等價於找到一堆$x_i(0\leq x_i\leq 1)$,使得下面式子的值最小:

\[
\max\left(\sum_{i=1}^n a_ix_i,\sum_{i=1}^n b_i(1-x_i)\right)
\]

由於

\[
\max\left(A,B\right)=\max_{0\leq k\leq 1}\left(kA+(1-k)B\right)
\]

因此

\begin{eqnarray*}
&&\max\left(\sum_{i=1}^n a_ix_i,\sum_{i=1}^n b_i(1-x_i)\right)\\
&=&\max_{0\leq k\leq 1}\left(\sum_{i=1}^n ka_ix_i+(1-k)b_i(1-x_i)\right)
\end{eqnarray*}

根據Minimax Theorem,有

\begin{eqnarray*}
&&\min_{0\leq x_1,x_2,\dots,x_n\leq 1}\left(\max_{0\leq k\leq 1}\left(\sum_{i=1}^n ka_ix_i+(1-k)b_i(1-x_i)\right)\right)\\
&=&\max_{0\leq k\leq 1}\left(\min_{0\leq x_1,x_2,\dots,x_n\leq 1}\left(\sum_{i=1}^n ka_ix_i+(1-k)b_i(1-x_i)\right)\right)\\
&=&\max_{0\leq k\leq 1}\left(\sum_{i=1}^n \min_{0\leq x_i\leq 1}\left(ka_ix_i+(1-k)b_i(1-x_i)\right)\right)\\
&=&\max_{0\leq k\leq 1}\left(\sum_{i=1}^n \min\left(ka_i,(1-k)b_i\right)\right)
\end{eqnarray*}

注意到對於固定的$i$來講,$\min\left(ka_i,(1-k)b_i\right)$關於$k$的函數是一個凸函數,而凸函數的和$f(k)$也爲凸函數,所以能夠經過三分這個$k$獲得答案。

對於固定的$i$來講,當$ka_i=(1-k)b_i$,也就是$k=\frac{b_i}{a_i+b_i}$時取得極值,因此只有$O(n)$個這樣的$k$是有用的,只須要在它們之間三分,也所以避免了浮點數運算。注意這裏須要對這些$k$進行去重,不然三分時可能會出現函數平臺致使三分失敗。

如今剩下的問題就是如何快速獲得答案。對於這些$k$創建一棵權值線段樹,將每一個二元組$(a_i,b_i)$放在分界線$\frac{b_i}{a_i+b_i}$的位置上。線段樹每一個節點維護對應區間內$a$的和、$b$的和以及區間左端點和右端點對應的$a$和$b$的和,插入一個新的二元組的時間複雜度爲$O(\log n)$。

查詢最優解時,只須要從線段樹根節點開始,假設左子樹表示$[l,mid]$,右子樹表示$[mid+1,r]$,那麼經過比較$f(mid)$和$f(mid+1)$的大小便可知道極值位於左子樹仍是右子樹,經過線段樹維護的信息能夠$O(1)$算出$f(mid)$和$f(mid+1)$的值。

時間複雜度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=250010,M=524305;
int Case,n,m,_,i,x,y,pos[N];ll sa[M],sb[M],la[M],lb[M],alla;
struct E{int x,y;}e[N];
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
struct Num{
  ll u,d;
  Num(){}Num(ll _u,ll _d){u=_u,d=_d;}
  void write(){
    ll z=gcd(u,d);
    printf("%lld/%lld\n",u/z,d/z);
  }
}q[N];
inline int cmp(const Num&a,const Num&b){
  ll t=a.u*b.d-b.u*a.d;
  if(t<0)return -1;
  return t?1:0;
}
inline bool cmpn(const Num&a,const Num&b){return cmp(a,b)<0;}
inline int lower(int A,int B){
  Num x(A,B);
  int l=1,r=m,mid,t;
  while(1){
    mid=(l+r)>>1;
    t=cmp(x,q[mid]);
    if(!t)return mid;
    if(t<0)r=mid-1;else l=mid+1;
  }
}
void build(int x,int a,int b){
  sa[x]=sb[x]=la[x]=lb[x]=0;
  if(a==b){pos[a]=x;return;}
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b);
}
inline void modify(int A,int B){
  int x=pos[lower(B,A+B)];
  alla+=A;
  sa[x]+=A;
  sb[x]+=B;
  la[x]+=A;
  lb[x]+=B;
  for(x>>=1;x;x>>=1){
    sa[x]+=A;
    sb[x]+=B;
    la[x]=la[x<<1];
    lb[x]=lb[x<<1];
  }
}
inline void query(){
  int x=1,a=1,b=m,mid;
  ll prea=0,preb=0;
  Num f,g;
  while(a<b){
    mid=(a+b)>>1;
    x<<=1;
    f=Num((alla-(prea+sa[x])-(preb+sb[x]))*q[mid].u+(preb+sb[x])*q[mid].d,q[mid].d);
    g=Num((alla-(prea+sa[x]+la[x+1])-(preb+sb[x]+lb[x+1]))*q[mid+1].u+(preb+sb[x]+lb[x+1])*q[mid+1].d,q[mid+1].d);
    if(cmp(f,g)<0){
      prea+=sa[x];
      preb+=sb[x];
      x++;
      a=mid+1;
    }else b=mid;
  }
  prea+=sa[x];
  preb+=sb[x];
  f=Num((alla-prea-preb)*q[a].u+preb*q[a].d,q[a].d);
  f.write();
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    q[1]=Num(0,1);
    q[m=2]=Num(1,1);
    for(i=1;i<=n;i++){
      scanf("%d%d",&x,&y);
      e[i].x=x;
      e[i].y=y;
      q[++m]=Num(y,x+y);
    }
    sort(q+1,q+m+1,cmpn);
    for(_=0,i=1;i<=m;i++)if(i==1||cmp(q[i],q[_]))q[++_]=q[i];
    m=_;
    build(1,1,m);
    alla=0;
    for(i=1;i<=n;i++)modify(e[i].x,e[i].y),query();
  }
}

  

E. Snowy Smile

首先將縱座標離散化到$O(n)$的範圍內,方便後續的處理。

將全部點按照橫座標排序,枚舉矩形的上邊界,而後日後依次加入每一個點,這樣就肯定了矩形的上下邊界。設$v[y]$表示矩形內部縱座標爲$y$的點的權值和,則答案爲$v$的最大子段和,用線段樹維護帶修改的最大子段和便可。

時間複雜度$O(n^2\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2010,M=4100;
int Case,n,m,i,j,k,cb,b[N],pos[N];ll pre[M],suf[M],s[M],v[M],ans;
struct E{int x,y,z;}e[N];
inline bool cmp(const E&a,const E&b){return a.x<b.x;}
void build(int x,int a,int b){
  pre[x]=suf[x]=s[x]=v[x]=0;
  if(a==b){
    pos[a]=x;
    return;
  }
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b);
}
inline void change(int x,int p){
  x=pos[x];
  s[x]+=p;
  if(s[x]>0)pre[x]=suf[x]=v[x]=s[x];else pre[x]=suf[x]=v[x]=0;
  for(x>>=1;x;x>>=1){
    pre[x]=max(pre[x<<1],s[x<<1]+pre[x<<1|1]);
    suf[x]=max(suf[x<<1|1],s[x<<1|1]+suf[x<<1]);
    s[x]=s[x<<1]+s[x<<1|1];
    v[x]=max(max(v[x<<1],v[x<<1|1]),suf[x<<1]+pre[x<<1|1]);
  }
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    for(cb=0,i=1;i<=n;i++){
      scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
      b[++cb]=e[i].y;
    }
    sort(b+1,b+cb+1);
    for(m=0,i=1;i<=cb;i++)if(i==1||b[i]!=b[m])b[++m]=b[i];
    sort(e+1,e+n+1,cmp);
    ans=0;
    for(i=1;i<=n;i++)e[i].y=lower_bound(b+1,b+m+1,e[i].y)-b;
    for(i=1;i<=n;i++)if(i==1||e[i].x!=e[i-1].x){
      build(1,1,m);
      for(j=i;j<=n;j=k){
        for(k=j;k<=n&&e[j].x==e[k].x;k++)change(e[k].y,e[k].z);
        if(ans<v[1])ans=v[1];
      }
    }
    printf("%lld\n",ans);
  }
}

  

F. Faraway

將$|x_i-x_e|+|y_i-y_e|$的絕對值拆掉,則每一個點$(x_i,y_i)$會將平面分割成$4$個部分,每一個部分裏距離的表達式沒有絕對值符號,一共$O(n^2)$個這樣的區域。

枚舉每一個區域,計算該區域中可能的終點數量。注意到$lcm(2,3,4,5)=60$,因此只須要枚舉$x_e$和$y_e$模$60$的餘數,$O(n)$判斷是否可行,而後$O(1)$計算該區域中有多少這樣的點便可。

時間複雜度爲$O(60^2n^3)$。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=15,K=60;
int Case,n,m,x,y,i,j,ca,cb,a[N],b[N];long long ans;
struct E{int x,y,k,t;}e[N];
inline int abs(int x){return x>0?x:-x;}
inline bool check(int x,int y){
  for(int i=0;i<n;i++)if((abs(x-e[i].x)+abs(y-e[i].y))%e[i].k!=e[i].t)return 0;
  return 1;
}
inline int cal(int l,int r){
  r-=l+1;
  if(r<0)return 0;
  return r/K+1;
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&m);
    a[ca=1]=b[cb=1]=m+1;
    for(i=0;i<n;i++){
      scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].k,&e[i].t);
      a[++ca]=e[i].x;
      b[++cb]=e[i].y;
    }
    sort(a+1,a+ca+1);
    sort(b+1,b+cb+1);
    ans=0;
    for(i=0;i<ca;i++)if(a[i]<a[i+1])for(j=0;j<cb;j++)if(b[j]<b[j+1])
      for(x=0;x<K;x++)for(y=0;y<K;y++)if(check(a[i]+x,b[j]+y))
        ans+=1LL*cal(a[i]+x,a[i+1])*cal(b[j]+y,b[j+1]);
    printf("%lld\n",ans);
  }
}

  

G. Support or Not

首先考慮找到第$k$小的球對距離,二分答案$mid$,統計有多少對球的距離不超過$mid$。咱們須要找到最小的$mid$,使得有至少$k$對球的距離不超過$mid$。

將每一個球的半徑都加上$\frac{mid}{2}$,那麼咱們的目標是統計有多少對球存在公共點。

假設最大的球半徑爲$R$,以$2R$爲棱長將三維空間劃分爲一個個立方體格子,那麼每一個球只須要檢查球心在附近$27$個格子內部的全部球。考慮全部球的半徑相等的狀況,那麼每一個格子內部一旦有超過$O(\sqrt{k})$個球時,咱們必然已經找到了$k$對相交的球。所以在找到$k$對相交的球時及時結束二分答案的檢查過程便可。

可是當球的半徑不盡相同時,上述分析不成立。那麼在當前球的半徑不足$\frac{R}{2}$時重構網格,則最多會重構$O(\log r)$次,且每一個球依然只會檢查均攤$O(\sqrt{k})$個球與它是否相交。

找到第$k$小解$ans$後,咱們只須要取$mid=k-1$,繼續運行檢查算法,將找到的這些相交球對之間的距離做爲最終的答案的便可,若是不足$k$個,那麼剩下的答案確定都是$ans$。

利用Hash表定位格子,則總時間複雜度爲$O(n\log^2r+n\sqrt{k}\log r)$,常數較小。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=100010,M=310,inf=3000000,MO=(1<<19)-1;
unsigned int wx[inf],wy[inf],wz[inf];
int Case,n,m,lim,K,i,ans[M];
struct E{int x,y,z,r;}e[N];
inline bool cmp(const E&a,const E&b){return a.r>b.r;}
inline ll sqr(ll x){return x*x;}
inline bool check(const E&a,const E&b){return sqr(a.x-b.x)+sqr(a.y-b.y)+sqr(a.z-b.z)<=sqr(a.r+b.r+lim*2);}
inline int dis(const E&a,const E&b){
  ll tmp=sqr(a.x-b.x)+sqr(a.y-b.y)+sqr(a.z-b.z);
  int l=0,r=inf,mid,ret;
  while(l<=r){
    mid=(l+r)>>1;
    if(tmp<=sqr(a.r+b.r+mid*2))r=(ret=mid)-1;else l=mid+1;
  }
  return ret;
}
struct EV{ull v;int w;EV*nxt;}*g[MO+7],pool[N],*cur,*p;
int pos[N],at[N],cnt,d[N],en[N],id[N],last[MO+7],CUR;
inline int ins(int A,int B,int C){
  int u=(wx[A]^wy[B]^wy[C])&MO;
  ull v=(((ull)A)<<42)|(((ull)B)<<21)|C;
  if(last[u]<CUR)last[u]=CUR,g[u]=NULL;
  for(p=g[u];p;p=p->nxt)if(p->v==v)return p->w;
  cnt++;
  d[cnt]=0;
  p=cur++;
  p->v=v;
  p->w=cnt;
  p->nxt=g[u];
  g[u]=p;
  return cnt;
}
inline int ask(int A,int B,int C){
  int u=(wx[A]^wy[B]^wy[C])&MO;
  ull v=(((ull)A)<<42)|(((ull)B)<<21)|C;
  if(last[u]<CUR)return 0;
  for(p=g[u];p;p=p->nxt)if(p->v==v)return p->w;
  return 0;
}
inline void build(int st,int pre){
  cnt=0;
  cur=pool;
  CUR++;
  for(int i=st;i<=n;i++){
    pos[i]=ins(e[i].x/pre,e[i].y/pre,e[i].z/pre);
    d[pos[i]]++;
  }
  for(int i=1;i<=cnt;i++)d[i]+=d[i-1];
  for(int i=1;i<=cnt;i++)en[i]=d[i];
  for(int i=st;i<=n;i++)id[d[pos[i]]--]=i;
}
inline int cal(int _lim,int mode=0){
  int pre=~0U>>1;
  lim=_lim;
  m=0;
  for(int i=1;i<=n;i++){
    int now=(e[i].r+lim)*2;
    if(now*2<pre&&i<n)build(i+1,pre=now);
    int A=e[i].x/pre,B=e[i].y/pre,C=e[i].z/pre;
    for(int x=A-1;x<=A+1;x++)if(x>=0)
      for(int y=B-1;y<=B+1;y++)if(y>=0)
        for(int z=C-1;z<=C+1;z++)if(z>=0){
          int o=ask(x,y,z);
          if(!o)continue;
          for(int j=en[o-1]+1;j<=en[o];j++){
            int k=id[j];
            if(k<=i)break;
            if(check(e[i],e[k])){
              m++;
              if(mode)ans[m]=dis(e[i],e[k]);
              if(m>=K)return m;
            }
          }
        }
  }
  return m;
}
int main(){
  for(wx[0]=324673,i=1;i<inf;i++)wx[i]=wx[i-1]*233+17;
  for(wy[0]=812376,i=1;i<inf;i++)wy[i]=wy[i-1]*13331+97;
  for(wz[0]=921375,i=1;i<inf;i++)wz[i]=wz[i-1]*10007+53;
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&K);
    for(i=1;i<=n;i++){
      scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].z,&e[i].r);
      e[i].x<<=1;
      e[i].y<<=1;
      e[i].z<<=1;
      e[i].r<<=1;
    }
    sort(e+1,e+n+1,cmp);
    int l=0,r=inf,mid,fin;
    while(l<=r){
      mid=(l+r)>>1;
      if(cal(mid)<K)l=mid+1;else r=(fin=mid)-1;
    }
    for(i=1;i<=K;i++)ans[i]=fin;
    if(fin)cal(fin-1,1);
    sort(ans+1,ans+K+1);
    for(i=1;i<=K;i++)printf("%d\n",ans[i]);
  }
}

  

H. TDL

考慮枚舉$f(n,m)-n$的值$t$,則$n=t\oplus k$,$O(t\log n)$檢查這個$n$是否知足條件便可。

注意到$t$顯然不會超過第$m$個與$n$互質的質數,而$n$最多隻有$O(\log\log n)<m=100$個質數,根據質數密度能夠獲得$t$的一個比較鬆的上界$O(m\log m)$。

時間複雜度$O(m^2\log^2m\log n)$。

#include<cstdio>
typedef long long ll;
int Case,m,d;ll k,ans;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
inline ll cal(ll n,int m){
  if(n<1)return 0;
  for(ll i=n+1;;i++)if(gcd(n,i)==1){
    m--;
    if(!m)return i-n;
  }
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%lld%d",&k,&m);
    ans=-1;
    for(d=1;d<700;d++)if(cal(k^d,m)==d){
      if(ans==-1)ans=k^d;
      else if(ans>(k^d))ans=k^d;
    }
    printf("%lld\n",ans);
  }
}

  

I. Three Investigators

考慮將數字$a[i]$拆成$a[i]$個$a[i]$,好比4,1,2$\rightarrow$4,4,4,4,1,2,2,則問題轉化爲:找到最多$5$個不共享元素的不降低子序列,使得這些子序列包含的元素總量最多。能夠證實,這等於楊氏圖表前$5$層的長度之和。

考慮楊氏圖表求解答案的過程:

  • 從$1$到$n$依次考慮序列中的每一個數,將其插入楊氏圖表的第一層中。
  • 插入$x$時,若是$x$不小於這一層的最大的數,則將$x$放在這一層的末尾;不然找到大於$x$的最小的數$y$,將$y$替換爲$x$,並將$y$插入下一層。

由於每一層的元素都有序,因此能夠用數組維護,尋找$y$的過程能夠用二分查找加速。

可是對於本題來講,咱們不能暴力地插入$a[i]$個$a[i]$。考慮將楊表每一層中相同的元素合併,用std::map記錄每一個元素的個數,那麼當咱們一次性插入$x$個$x$時,只須要將其插入std::map中,而後不斷消費後繼,將後繼的元素個數減小便可,在減小的時候要將其做爲「$p$個$q$」插入下一層中。

每一類數字被消費完畢後須要及時從std::map中刪除,而每次插入會致使最多一種其它數字被拆分,因此每層的插入次數至多爲上一層的兩倍。

假設要求不超過$k$個子序列的答案,本題中$k=5$,則時間複雜度爲$O(2^kn\log n)$。

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int K=5;
int Case,n,i,x;ll ans;map<int,ll>T[K];
void ins(int o,int x,ll p){
  if(o>=K)return;
  T[o][x]+=p;
  ans+=p;
  while(p){
    map<int,ll>::iterator it=T[o].lower_bound(x+1);
    if(it==T[o].end())return;
    ll t=min(p,it->second);
    ans-=t;
    p-=t;
    ins(o+1,it->first,t);
    if(t==it->second)T[o].erase(it);else it->second-=t;
  }
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    ans=0;
    for(i=0;i<K;i++)T[i].clear();
    for(i=1;i<=n;i++){
      scanf("%d",&x);
      ins(0,x,x);
      printf("%lld%c",ans,i<n?' ':'\n');
    }
  }
}

  

J. Ridiculous Netizens

取一個根,將這棵樹轉化爲有根樹,考慮連通塊包含根節點的狀況,那麼對於一個點來講,若是它選了,它的父親就必須選。

求出DFS序括號序列,設$f[i][\lfloor\frac{m}{j}\rfloor]$表示考慮了DFS序的前$i$項,目前連通塊點權乘積爲$j$的方案數。由於當$j\geq\sqrt{m}$時$\lfloor\frac{m}{j}\rfloor$只有$O(\sqrt{m})$種取值,因此狀態數爲$O(n\sqrt{m})$。注意到$\lfloor\frac{m}{jk}\rfloor=\lfloor\frac{\lfloor\frac{m}{j}\rfloor}{k}\rfloor$,因此能夠轉移。

若是$i$是一個左括號,那麼把$f$傳給兒子,並強制選擇兒子;若是$i$是個右括號,那麼這個子樹既能夠選又能夠不選,將對應狀態的方案數累加便可,轉移$O(1)$。

接下來考慮連通塊不包含根節點的狀況,那麼能夠去掉這個根,變成若干棵樹的子問題。取重心做爲根進行點分治,則考慮的總點數爲$O(n\log n)$。

時間複雜度$O(n\sqrt{m}\log n)$。

#include<cstdio>
const int N=2010,K=2010,P=1000000007;
int Case,n,m,cnt,val[K],i,x,y,a[N],ans;
int g[N],nxt[N<<1],v[N<<1],ok[N<<1],ed,son[N],f[N],all,now;
int dp[N][K],tmp[K];
inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;}
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];ok[ed]=1;g[x]=ed;}
void findroot(int x,int y){
  son[x]=1;f[x]=0;
  for(int i=g[x];i;i=nxt[i])if(ok[i]&&v[i]!=y){
    findroot(v[i],x);
    son[x]+=son[v[i]];
    if(son[v[i]]>f[x])f[x]=son[v[i]];
  }
  if(all-son[x]>f[x])f[x]=all-son[x];
  if(f[x]<f[now])now=x;
}
void dfs(int x,int y){
  int i,j,k=a[x];
  for(i=1;i<=cnt;i++)tmp[i]=0;
  for(i=j=1;i<=cnt;i++){
    int t=val[i]/k;
    if(!t)continue;
    while(val[j]>t)j++;
    up(tmp[j],dp[x][i]);
  }
  for(i=1;i<=cnt;i++)dp[x][i]=tmp[i];
  for(i=g[x];i;i=nxt[i])if(ok[i]){
    int u=v[i];
    if(u==y)continue;
    for(j=1;j<=cnt;j++)dp[u][j]=dp[x][j];
    dfs(u,x);
    for(j=1;j<=cnt;j++)up(dp[x][j],dp[u][j]);
  }
}
void solve(int x){
  int i;
  for(i=1;i<=cnt;i++)dp[x][i]=0;
  dp[x][1]=1;
  dfs(x,0);
  for(i=1;i<=cnt;i++)up(ans,dp[x][i]);
  for(i=g[x];i;i=nxt[i])if(ok[i]){
    ok[i^1]=0;
    f[0]=all=son[v[i]];
    findroot(v[i],now=0);
    solve(now);
  }
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&m);
    cnt=ans=0;
    for(i=1;i<=n;i++)g[i]=son[i]=f[i]=0;
    for(i=1;i<=m;i=m/(m/i)+1)val[++cnt]=m/i;
    for(i=1;i<=n;i++)scanf("%d",&a[i]);
    for(ed=i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x);
    f[0]=all=n;findroot(1,now=0);solve(now);
    printf("%d\n",ans);
  }
}

  

K. 11 Dimensions

設$f[i][j]$表示$[1,i-1]$這些位的數字已經肯定,且$[1,i-1]$的數字模$m=j$時,有多少種在$[i,n]$這些位填數字的方法使得最終的數字模$m=0$。

初始值:$f[n+1][0]=1$。

轉移:$f[i][j]=\sum f[i+1][(10j+k)\bmod m]$,其中$0\leq k\leq 9$,且第$i+1$位能夠填$k$。

最終知足條件的總方案數即爲$f[1][0]$,這樣就能夠判斷每一個詢問是否有解。

考慮在有解的狀況下如何找到第$k$小的方案。咱們稱若是狀態$i$由狀態$j$等累加獲得,則$j$是$i$的一個後繼狀態。從初始狀態$(1,0)$開始,按照下一位填的數字從小到大枚舉當前狀態$S$的每一個後繼狀態$T$,若是$T$的DP值$\geq k$,則說明咱們要找的方案在$T$中,且這個方案下這一位已經肯定,而後走到$T$狀態便可;不然$T$的DP值$<k$,那麼將$k$減去$T$的DP值,而後繼續考慮其它更大的後繼便可。這樣單次詢問是$O(n)$的,不能接受。

相似樹的輕重鏈剖分,對於每一個狀態,取其後繼狀態中DP值最大的狀態做爲重後繼,則每一個狀態最多隻有一個重後繼,咱們能夠倍增求出每一個狀態日後走$2^k$次重後繼後會到達哪一個狀態,以及那個狀態相對當前來講是第幾小的方案。對於每一個詢問,咱們先在倍增數組中沿着重後繼不斷往前走直到必需要走輕後繼爲止,而後走一次輕後繼,再接着沿着倍增數組走重後繼。

由於重後繼是DP值最大的後繼,這意味着每一個輕後繼的DP值不超過總方案數的一半,因此每走一次輕後繼,$k$至少會除以二,最多$O(\log k)$次。

時間複雜度$O(nm\log n+q\log n\log k)$。

#include<cstdio>
typedef long long ll;
const ll inf=1000000000000000010LL;
const int N=50010,M=20,K=17,P=1000000007;
int Case,n,m,q,i,j,k,p[N];ll _;
char a[N];
int g[M][10];
bool can[N][10];
ll f[N][M],st[K][N][M],en[K][N][M];
char go[K][N][M];
int val[K][N][M];
inline ll fix(ll x){return x<inf?x:inf;}
inline int query(ll k){
  if(k>f[1][0])return -1;
  int x=1,y=0,ret=0,i;
  while(x<=n){
    for(i=K-1;~i;i--)if(x+(1<<i)<=n+1&&st[i][x][y]<k&&k<=en[i][x][y]){
      ret=(1LL*ret*p[1<<i]+val[i][x][y])%P;
      k-=st[i][x][y];
      y=go[i][x][y];
      x+=1<<i;
    }
    if(x>n)break;
    for(i=0;i<10;i++)if(can[x][i]){
      ll tmp=f[x+1][g[y][i]];
      if(k>tmp)k-=tmp;
      else{
        ret=(10LL*ret+i)%P;
        x++;
        y=g[y][i];
        break;
      }
    }
  }
  return ret;
}
int main(){
  for(p[0]=i=1;i<N;i++)p[i]=10LL*p[i-1]%P;
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d%d%s",&n,&m,&q,a+1);
    for(i=0;i<m;i++)for(j=0;j<10;j++)g[i][j]=(i*10+j)%m;
    for(i=1;i<=n;i++){
      if(a[i]=='?')for(j=0;j<10;j++)can[i][j]=1;
      else{
        for(j=0;j<10;j++)can[i][j]=0;
        can[i][a[i]-'0']=1;
      }
    }
    for(j=0;j<m;j++)f[n+1][j]=j==0;
    for(i=n;i;i--)for(j=0;j<m;j++){
      ll tmp=0;
      int nxt=-1;
      ll sz=-1;
      for(k=0;k<10;k++)if(can[i][k]){
        ll now=f[i+1][g[j][k]];
        tmp=fix(tmp+now);
        if(now>sz)nxt=k,sz=now;
      }
      f[i][j]=tmp;
      go[0][i][j]=g[j][nxt];
      val[0][i][j]=nxt;
      ll sum=0;
      for(k=0;k<nxt;k++)if(can[i][k])sum=fix(sum+f[i+1][g[j][k]]);
      st[0][i][j]=sum;
      en[0][i][j]=fix(sum+f[i+1][g[j][nxt]]);
    }
    for(k=1;k<K;k++)for(i=1;i+(1<<k)<=n+1;i++)for(j=0;j<m;j++){
      int x=go[k-1][i][j],len=1<<(k-1);
      go[k][i][j]=go[k-1][i+len][x];
      val[k][i][j]=(1LL*val[k-1][i][j]*p[len]+val[k-1][i+len][x])%P;
      st[k][i][j]=fix(st[k-1][i][j]+st[k-1][i+len][x]);
      en[k][i][j]=fix(st[k-1][i][j]+en[k-1][i+len][x]);
    }
    while(q--)scanf("%lld",&_),printf("%d\n",query(_));
  }
}

  

L. Stay Real

小根堆中,每一個點的權值老是不小於父親節點的權值。因此不管怎麼取,先拿走的數必定不小於後面拿走的數。

此時雙方的最優策略就是:貪心選擇能取的數字之中最大的數。

時間複雜度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100010;
int Case,n,i,j,a[N];ll A,B;
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    A=B=0;
    for(i=n,j=1;i;i--,j^=1)if(j)A+=a[i];else B+=a[i];
    printf("%lld %lld\n",A,B);
  }
}
相關文章
相關標籤/搜索