1. 程式人生 > >2018年12月30&31日

2018年12月30&31日

小結:昨天由於做的題目比較少,所以就和今天寫在一塊了,昨天學習了差分約束和樹上差分,當然樹上差分是用線段樹來維護的,今天重點整理了部落格\(233\),然後做了幾個題。

一. 完成的題目:

洛谷P3275,洛谷P4878,洛谷P2294,洛谷P3258,洛谷P3038,洛谷P1262,洛谷P5159,洛谷P4113

二.

1.當日完成題目數:8道。

2. 未完成6個題目的原因:

3. 複習的知識點:樹鏈剖分,線段樹,差分約束,tarjan,數論,樹狀陣列

4.不會題目:洛谷P5160

三:

1. 洛谷P3275 [SCOI2011]糖果

題目描述

幼兒園裡有\(N\)個小朋友,\(lxhgww\)

老師現在想要給這些小朋友們分配糖果,要求每個小朋友都要分到糖果。但是小朋友們也有嫉妒心,總是會提出一些要求,比如小明不希望小紅分到的糖果比他的多,於是在分配糖果的時候,\(lxhgww\)需要滿足小朋友們的\(K\)個要求。幼兒園的糖果總是有限的,\(lxhgww\)想知道他至少需要準備多少個糖果,才能使得每個小朋友都能夠分到糖果,並且滿足小朋友們所有的要求。

輸入輸出格式

輸入格式:

輸入的第一行是兩個整數\(N\)\(K\)。接下來K行,表示這些點需要滿足的關係,每行\(3\)個數字,\(X\)\(A\)\(B\)。如果\(X=1\), 表示第\(A\)個小朋友分到的糖果必須和第\(B\)

個小朋友分到的糖果一樣多;如果\(X=2\), 表示第\(A\)個小朋友分到的糖果必須少於第\(B\)個小朋友分到的糖果;如果\(X=3\), 表示第\(A\)個小朋友分到的糖果必須不少於第\(B\)個小朋友分到的糖果;如果\(X=4\), 表示第\(A\)個小朋友分到的糖果必須多於第\(B\)個小朋友分到的糖果;如果\(X=5\), 表示第\(A\)個小朋友分到的糖果必須不多於第\(B\)個小朋友分到的糖果;

輸出格式:

輸出一行,表示\(lxhgww\)老師至少需要準備的糖果數,如果不能滿足小朋友們的所有要求,就輸出\(-1\)

輸入輸出樣例

輸入樣例#1:

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1

輸出樣例#1:

11

說明

【資料範圍】

對於\(30\%\)的資料,保證 \(N \leq 100\)

對於\(100\%\)的資料,保證 \(N \leq 100000\)

對於所有的資料,保證 \(K \leq 100000\)\(1 \leq X \leq 5\)\(1 \leq A, B \leq N\)

思路:考慮差分約束,對於給出的第一種關係,我們就建一條雙向的邊權為\(0\)的邊,然後第二種情況,\(A\)\(B\)小,而且要嚴格小於,那就相當於是\(d[B]-d[A]>=1\),然後剩餘的三種就跟前兩種類似了,就不必多說了,然後跑最短路的時候,我們以\(0\)為起點,向其它的點建一條權值為\(0\)的邊,然後無解就是存在負環或建邊的時候起點等於終點。

程式碼:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<queue>
#define ll long long
#define maxn 100007
using namespace std;
int num,n,k,head[maxn],dis[maxn],vis[maxn],in[maxn];
ll ans;
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,w,nxt;
}e[300007];
inline void ct(int u, int v, int w) {
  e[++num].v=v;
  e[num].w=w;
  e[num].nxt=head[u];
  head[u]=num;
}
inline int spfa() {
  memset(dis,-0x3f,sizeof(dis));
  queue<int>q;
  q.push(0),dis[0]=0,vis[0]=1,in[0]=1;
  while(!q.empty()) {
    int u=q.front();q.pop();
    vis[u]=0;
    for(int i=head[u];i;i=e[i].nxt) {
      int v=e[i].v;
      if(dis[v]<dis[u]+e[i].w) {
        dis[v]=dis[u]+e[i].w;
        if(!vis[v]) {
          vis[v]=1;
          in[v]++;
          if(in[v]>n) return -1;
          q.push(v);
        }
      }
    }
  }
  return 0;
}
int main() {
  n=qread(),k=qread();
  for(int i=1,p,u,v;i<=k;++i) {
    p=qread(),u=qread(),v=qread();
    if(p==1) ct(u,v,0),ct(v,u,0);
    if(p==2) {
      if(u==v) {
        printf("-1\n");
        return 0;
      }
      ct(u,v,1);
    }
    if(p==3) ct(v,u,0);
    if(p==4) {
      if(u==v) {
        printf("-1\n");
        return 0;
      }
      ct(v,u,1);
    }
    if(p==5) ct(u,v,0);
  }
  for(int i=n;i>=1;--i) ct(0,i,1);
  if(spfa()) {
    printf("-1\n");
    return 0;
  }
  for(int i=1;i<=n;++i) ans+=dis[i];
  printf("%lld\n",ans);
  return 0;
}

2. 洛谷P4878 [USACO05DEC]layout佈局

題目描述

正如其他物種一樣,奶牛們也喜歡在排隊打飯時與它們的朋友挨在一起。\(FJ\) 有編號為 \(1\dots N\)\(N\) 頭奶牛 \((2\le N\le 1000)\)。開始時,奶牛們按照編號順序來排隊。奶牛們很笨拙,因此可能有多頭奶牛在同一位置上。

有些奶牛是好基友,它們希望彼此之間的距離小於等於某個數。有些奶牛是情敵,它們希望彼此之間的距離大於等於某個數。

給出 \(M_L\)​ 對好基友的編號,以及它們希望彼此之間的距離小於等於多少;又給出 \(M_D\)​ 對情敵的編號,以及它們希望彼此之間的距離大於等於多少 \((1≤M_L​, M_D\le 10^4)\)

請計算:如果滿足上述所有條件,\(1\) 號奶牛和 \(N\) 號奶牛之間的距離最大為多少。

輸入輸出格式

輸入格式

第一行:三個整數 \(N, M_L, M_D\)​,用空格分隔。

\(2\dots M_L+1\) 行:每行三個整數 \(A, B, D\),用空格分隔,表示 \(A\) 號奶牛與 \(B\) 號奶牛之間的距離須 \(\le D\)。保證 \(1\le A<B\le N\), \(1\le D\le 10^6\).

\(M_L+2\dots M_L+M_D+1\) 行:每行三個整數 \(A, B, D\),用空格分隔,表示 \(A\) 號奶牛與 \(B\) 號奶牛之間的距離須 \(\ge D\)。保證 \(1\le A<B\le N\), \(1\le D\le 10^6\).

輸出格式

一行,一個整數。如果沒有合法方案,輸出 \(-1\). 如果有合法方案,但 \(1\) 號奶牛可以與 \(N\) 號奶牛相距無窮遠,輸出\(-2\). 否則,輸出 \(1\) 號奶牛與 \(N\) 號奶牛間的最大距離。

輸入輸出樣例

輸入樣例#1:

4 2 1
1 3 10
2 4 20
2 3 3

輸出樣例#1:

27

思路:

做這道題之前,大家應該先學習一下差分約束。

給大家推薦個部落格:不是我的……

然後回到這個題目上來,首先這道題有負環的出現,那顯然不能用\(dijkstra\)了,那就把解法鎖定為\(spfa\)

對於給出的前\(M_L\)種關係:

就是這個樣子:\(d[B]-d[A] \leq D\)

\(M_D\)種關係是:\(d[B]-d[A] \geq D\),即\(d[A]-d[B] \leq -D\)

那對於第一種關係,根據差分約束,我們需要建從\(A\)\(B\),邊權為D的邊,對於第二種關係,我們就需要建從\(B\)\(A\),邊權為\(-D\)的邊。

這樣建完圖跑直接呼叫\(spfa(1)\)就可以得\(70\)分了,那為什麼不能\(AC\)
呢?

因為我們差分約束時的起點是\(0\),所以我們要先跑一遍\(spfa(0)\),並建一條從0到\(i(1 \leq i \leq n)\)的邊權為0的邊,為什麼呢?

難道不應該是\(d[0]-d[i] \leq 0\)麼?這樣不是應該從i到0的邊麼?但是,有沒有想過,如果你這樣建,那你以0為起點跑spfa有意義麼?0沒法到達任何一個點,所以,我們需要建一條從0到i的邊的邊權為0的邊。這樣這題就能AC啦!

自己整理的題解

下面是我醜陋(學長說的)的程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<queue>
#define maxn 1007
using namespace std;
int num,n,m,p,head[maxn],dis[maxn],vis[maxn],in[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,w,nxt;
}e[20007];
inline void ct(int u, int v, int w) {
  e[++num].v=v;
  e[num].w=w;
  e[num].nxt=head[u];
  head[u]=num;
}
const int inf=0x3f3f3f3f;
inline void spfa(int s) {
  memset(dis,0x3f,sizeof(dis));
  memset(vis,0,sizeof(vis));
  memset(in,0,sizeof(in));
  queue<int>q;
  q.push(s),vis[s]=1,in[s]=1;
  dis[s]=0;
  while(!q.empty()) {
    int u=q.front();
    q.pop();vis[u]=0;
    for(int i=head[u];i;i=e[i].nxt) {
      int v=e[i].v;
      if(dis[v]>dis[u]+e[i].w) {
        dis[v]=dis[u]+e[i].w;
        if(!vis[v]) {
          vis[v]=1;
          in[v]++;
          if(in[v]>n) {
            printf("-1\n");
            exit(0);
          }
          q.push(v);
        }
      }
    }
  }
}
int main() {
  n=qread(),m=qread(),p=qread();
  for(int i=1,u,v,d;i<=m;++i) {
    u=qread(),v=qread(),d=qread();
    ct(u,v,d);
  }
  for(int i=1,u,v,d;i<=p;++i) {
    u=qread(),v=qread(),d=qread();
    ct(v,u,-d);
  }
  for(int i=1;i<=n;++i) ct(0,i,0);
  spfa(0);
  spfa(1);
  if(dis[n]==inf) {
    printf("-2\n");
    return 0;
  }
  printf("%d\n",dis[n]);
  return 0;
}

3. 洛谷P2294 [HNOI2005]狡猾的商人

題目描述

輸入輸出格式

輸入格式:

從檔案\(input.txt\)中讀入資料,檔案第一行為一個正整數\(w\),其中\(w < 100\),表示有\(w\)組資料,即\(w\)個賬本,需要你判斷。每組資料的第一行為兩個正整數\(n\)\(m\),其中\(n < 100,m < 1000\),分別表示對應的賬本記錄了多少個月的收入情況以及偷看了多少次賬本。接下來的\(m\)行表示刁奼偷看\(m\)次賬本後記住的\(m\)條資訊,每條資訊佔一行,有三個整數\(s\)\(t\)\(v\),表示從第\(s\)個月到第\(t\)個月(包含第\(t\)個月)的總收入為\(v\),這裡假設\(s\)總是小於等於\(t\)

輸出格式:

輸出檔案\(output.txt\)中包含\(w\)行,每行是\(true\)\(false\),其中第\(i\)行為\(true\)當且僅當第\(i\)組資料,即第\(i\)個賬本不是假的;第\(i\)行為\(false\)當且僅當第i組資料,即第\(i\)個賬本是假的。

輸入輸出樣例

輸入樣例#1:

2
3 3
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51

輸出樣例#1:

true
false

思路:題目中的條件可以轉化為\(v \leq d[t]-d[s] \leq v\),這就是這個題目中唯一的約束條件,然後對於每組詢問建邊,跑\(spfa\)即可,如果說在第i組詢問中存在負環,說明第\(i\)本賬本是假的,輸出\(false\),否則輸出\(true\)

程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<queue>
#define maxn 100007
using namespace std;
int num,t,n,m,head[maxn],dis[maxn],vis[maxn],in[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,w,nxt;
}e[200007];
inline void ct(int u, int v, int w) {
  e[++num].v=v;
  e[num].w=w;
  e[num].nxt=head[u];
  head[u]=num;
}
inline bool spfa() {
  memset(dis,0x3f,sizeof(dis));
  memset(vis,0,sizeof(vis));
  memset(in,0,sizeof(in));
  queue<int>q;
  q.push(0),vis[0]=1,in[0]=1,dis[0]=0;
  while(!q.empty()) {
    int u=q.front();
    q.pop(),vis[u]=0;
    for(int i=head[u];i;i=e[i].nxt) {
      int v=e[i].v;
      if(dis[v]>dis[u]+e[i].w) {
        dis[v]=dis[u]+e[i].w;
        if(!vis[v]) {
          vis[v]=1;
          in[v]++;
          if(in[v]>n) return 1;
          q.push(v);
        }
      }
    }
  }
  return 0;
}
int main() {
  t=qread();
  while(t--) {
    n=qread(),m=qread();
    memset(head,0,sizeof(head));
    num=0;
    for(int i=1,u,v,w;i<=m;++i) {
      u=qread(),v=qread(),w=qread();
      ct(u-1,v,w),ct(v,u-1,-w);
    }
    if(spfa()) printf("false\n");
    else printf("true\n");
  }
  return 0;
}

4. P3258 [JLOI2014]松鼠的新家

題目描述

松鼠的新家是一棵樹,前幾天剛剛裝修了新家,新家有n個房間,並且有\(n-1\)根樹枝連線,每個房間都可以相互到達,且倆個房間之間的路線都是唯一的。天哪,他居然真的住在”樹“上。

松鼠想邀請小熊維尼前來參觀,並且還指定一份參觀指南,他希望維尼能夠按照他的指南順序,先去\(a_1\),再去\(a_2\),......,最後到\(a_n\),去參觀新家。可是這樣會導致維尼重複走很多房間,懶惰的維尼不停地推辭。可是松鼠告訴他,每走到一個房間,他就可以從房間拿一塊糖果吃。

維尼是個饞傢伙,立馬就答應了。現在松鼠希望知道為了保證維尼有糖果吃,他需要在每一個房間各放至少多少個糖果。

因為松鼠參觀指南上的最後一個房間\(a_n\)是餐廳,餐廳裡他準備了豐盛的大餐,所以當維尼在參觀的最後到達餐廳時就不需要再拿糖果吃了。

輸入輸出格式

輸入格式:

第一行一個整數\(n\),表示房間個數第二行\(n\)個整數,依次描述\(a_1-a_n\)

接下來\(n-1\)行,每行兩個整數\(x\)\(y\),表示標號\(x\)\(y\)的兩個房間之間有樹枝相連。

輸出格式:

一共\(n\)行,第\(i\)行輸出標號為\(i\)的房間至少需要放多少個糖果,才能讓維尼有糖果吃。

輸入輸出樣例

輸入樣例#1:

5
1 4 5 3 2
1 2
2 4
2 3
4 5

輸出樣例#1:

1
2
1
2
1

說明

\(2 \leq n \leq 300000\)

思路:考慮樹鏈剖分+線段樹,我們自己手玩樣例可以發現,我們按題目中給出的結點訪問順序進行路徑修改,最後把每條路徑的起點的點權\(-1\)(除了起始結點),就是答案了,然後就是一個樹鏈剖分的裸題了。

程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 300007
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,m,head[maxn],d[maxn],son[maxn],siz[maxn],id[maxn],w[maxn];
int num,cnt,sum[maxn<<2],top[maxn],fa[maxn],lazy[maxn<<2];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[maxn<<1];
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
}
inline void pushdown(int rt) {
  if(lazy[rt]) {
    sum[ls]+=lazy[rt],sum[rs]+=lazy[rt];
    lazy[ls]+=lazy[rt],lazy[rs]+=lazy[rt];
    lazy[rt]=0;
  }
}
void modify(int rt, int l, int r, int L, int R, int val) {
  if(L>r||R<l) return;
  if(L<=l&&r<=R) {
    sum[rt]+=val;
    lazy[rt]+=val;
    return;
  }
  pushdown(rt);
  int mid=(l+r)>>1;
  modify(ls,l,mid,L,R,val),modify(rs,mid+1,r,L,R,val);
}
int query(int rt, int l, int r, int L) {
  if(l==r) return sum[rt];
  int mid=(l+r)>>1;
  pushdown(rt);
  if(L<=mid) return query(ls,l,mid,L);
  else return query(rs,mid+1,r,L);
}
void dfs1(int u) {
  siz[u]=1;
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(v!=fa[u]) {
      d[v]=d[u]+1;
      fa[v]=u;
      dfs1(v);
      siz[u]+=siz[v];
      if(siz[v]>siz[son[u]]) son[u]=v;
    }
  }
}
void dfs2(int u, int t) {
  id[u]=++cnt;
  top[u]=t;
  if(son[u]) dfs2(son[u],t);
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
  }
}
void cal(int x, int y) {
  int fx=top[x],fy=top[y];
  while(fx!=fy) {
    if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
    modify(1,1,cnt,id[fx],id[x],1);
    x=fa[fx],fx=top[x];
  }
  if(id[x]>id[y]) swap(x,y);
  modify(1,1,cnt,id[x],id[y],1);
}
int main() {
  n=qread();
  for(int i=1;i<=n;++i) w[i]=qread();
  for(int i=1,u,v;i<n;++i) {
    u=qread(),v=qread();
    ct(u,v);ct(v,u);
  }
  dfs1(1),dfs2(1,1);
  int now=w[1];
  for(int i=2;i<=n;++i) {
    cal(now,w[i]);
    now=w[i];
    modify(1,1,cnt,id[now],id[now],-1);
  }
  for(int i=1;i<=n;++i) printf("%d\n",query(1,1,cnt,id[i]));
  return 0;
}

5. 洛谷P3038 牧草種植Grass Planting

思路:

首先,這道題的翻譯是有問題的(起碼現在是),查詢的時候應該是查詢某一條路徑的權值,而不是某條邊(坑死我了)。

與平常樹鏈剖分題目不同的是,這道題目維護的是邊權,而不是點權,那怎麼辦呢?好像有點棘手誒,這是一種非常經典的題型,我們可以發現,一個點最多隻有一個父親!!!那,我們顯然就可以用這個點的點權去代替它與它父親之間的邊權!!!然後這道題不就成了樹鏈剖分水題了嘛?剛開始邊權都是\(0\),那我們就根據題目給的邊建邊權為\(0\)的邊。

\(nonono\),還有一個坑點就是在路徑查詢和修改的時候,兩點的\(LCA\)的點權是不能算在其中的,因為它的點權是\(LCA\)\(LCA\)父親之間邊的邊權,注意這幾個問題,那這題就真的是水題了!

自己整理的題解

具體實現看程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 100007
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
int n,m,head[maxn],d[maxn],son[maxn],siz[maxn],id[maxn],w[maxn];
int num,cnt,sum[maxn<<2],lazy[maxn<<2],top[maxn],fa[maxn],a[maxn];
char s[3];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,w,nxt;
}e[maxn<<1];
inline void ct(int u, int v, int w) {
  e[++num].v=v;
  e[num].w=w;
  e[num].nxt=head[u];
  head[u]=num;
}
inline void pushup(int rt) {
  sum[rt]=sum[ls]+sum[rs];
}
void build(int rt, int l, int r) {
  if(l==r) {
    sum[rt]=a[l];
    return;
  }
  int mid=(l+r)>>1;
  build(ls,l,mid);
  build(rs,mid+1,r);
  pushup(rt);
}
inline void pushdown(int rt, int len) {
  if(lazy[rt]) {
    sum[ls]+=(len-(len>>1))*lazy[rt];
    sum[rs]+=(len>>1)*lazy[rt];
    lazy[ls]+=lazy[rt],lazy[rs]+=lazy[rt];
    lazy[rt]=0;
  }
}
void modify(int rt, int l, int r, int L, int R, int val) {
  if(L>r||R<l) return;
  if(L<=l&&r<=R) {
    sum[rt]+=(r-l+1)*val;
    lazy[rt]+=val;
    return;
  }
  pushdown(rt,r-l+1);
  int mid=(l+r)>>1;
  modify(ls,l,mid,L,R,val),modify(rs,mid+1,r,L,R,val);
  pushup(rt);
}
int csum(int rt, int l, int r, int L, int R) {
  if(L>r||R<l) return 0;
  if(L<=l&&r<=R) return sum[rt];
  pushdown(rt,r-l+1);
  int mid=(l+r)>>1;
  return csum(ls,l,mid,L,R)+csum(rs,mid+1,r,L,R);
}
void dfs1(int u) {
  siz[u]=1;
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(v!=fa[u]) {
      d[v]=d[u]+1;
      fa[v]=u;
      w[u]=e[i].w;
      dfs1(v);
      siz[u]+=siz[v];
      if(siz[v]>siz[son[u]]) son[u]=v;
    }
  }
}
void dfs2(int u, int t) {
  id[u]=++cnt;
  top[u]=t;
  a[cnt]=w[u];
  if(son[u]) dfs2(son[u],t);
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
  }
}
void cal(int x, int y) {
  int fx=top[x],fy=top[y];
  while(fx!=fy) {
    if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
    modify(1,1,cnt,id[fx],id[x],1);
    x=fa[fx],fx=top[x];
  }
  if(id[x]>id[y]) swap(x,y);
  modify(1,1,cnt,id[x]+1,id[y],1);
}
int query(int x, int y) {
  int fx=top[x],fy=top[y],ans=0;
  while(fx!=fy) {
    if(d[fx]<d[fy]) swap(x,y),swap(fx,fy);
    ans+=csum(1,1,cnt,id[fx],id[x]);
    x=fa[fx],fx=top[x];
  }
  if(id[x]>id[y]) swap(x,y);
  ans+=csum(1,1,cnt,id[x]+1,id[y]);
  return ans;
}
int main() {
  n=qread(),m=qread();
  for(int i=1,u,v;i<n;++i) {
    u=qread(),v=qread();
    ct(u,v,0);ct(v,u,0);
  }
  dfs1(1);dfs2(1,1);build(1,1,n);
  for(int i=1,u,v;i<=m;++i) {
    scanf("%s",s);
    u=qread(),v=qread();
    if(s[0]=='P') cal(u,v);
    else printf("%d\n",query(u,v));
  }
  return 0;
}

6. 洛谷P1262 間諜網路

題目描述

由於外國間諜的大量滲入,國家安全正處於高度的危機之中。如果\(A\)間諜手中掌握著關於\(B\)間諜的犯罪證據,則稱\(A\)可以揭發\(B\)。有些間諜收受賄賂,只要給他們一定數量的美元,他們就願意交出手中掌握的全部情報。所以,如果我們能夠收買一些間諜的話,我們就可能控制間諜網中的每一分子。因為一旦我們逮捕了一個間諜,他手中掌握的情報都將歸我們所有,這樣就有可能逮捕新的間諜,掌握新的情報。

我們的反間諜機關提供了一份資料,包括所有已知的受賄的間諜,以及他們願意收受的具體數額。同時我們還知道哪些間諜手中具體掌握了哪些間諜的資料。假設總共有\(n\)個間諜(\(n\)不超過\(3000\)),每個間諜分別用\(1\)\(3000\)的整數來標識。

請根據這份資料,判斷我們是否有可能控制全部的間諜,如果可以,求出我們所需要支付的最少資金。否則,輸出不能被控制的一個間諜。

輸入輸出格式

輸入格式:

第一行只有一個整數\(n\)

第二行是整數\(p\)。表示願意被收買的人數,\(1≤p≤n\)

接下來的\(p\)行,每行有兩個整數,第一個數是一個願意被收買的間諜的編號,第二個數表示他將會被收買的數額。這個數額不超過\(20000\)

緊跟著一行只有一個整數\(r\)\(1≤r≤8000\)。然後\(r\)行,每行兩個正整數,表示數對\((A, B)\)\(A\)間諜掌握\(B\)間諜的證據。

輸出格式:

如果可以控制所有間諜,第一行輸出YES,並在第二行輸出所需要支付的賄金最小值。否則輸出NO,並在第二行輸出不能控制的間諜中,編號最小的間諜編號。

輸入輸出樣例

輸入樣例#1:

3
2
1 10
2 100
2
1 3
2 3

輸出樣例#1:

YES
110

輸入樣例#2:

4
2
1 100
4 200
2
1 2
3 4

輸出樣例#2:

NO
3

思路:

可以看出此題有兩種情況:

一是有的罪犯既不能賄賂他也沒有罪犯能揭發他,那麼此題無解,我們在遍歷時打上標記,然後從小到大列舉,只要遇見沒有標記的就輸出然後退出即可。

二是所有的罪犯都能直接或間接地被能賄賂的罪犯揭發。很明顯,也有兩種情況,一是沒有環,那麼資金就是賄賂那個沒有入度的罪犯,二是有環,那麼資金就是那個環裡罪犯所需資金最小的。我們想,如果我們把環裡的罪犯縮成一個點,那麼全都是前者的情況了。然後就可以\(tarjan\)縮點做了。

程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<stack>
#define maxn 3007
using namespace std;
int num,n,m,vis[maxn],cnt,js,head[maxn],dfn[maxn],low[maxn];
int bel[maxn],block[maxn],w[maxn],p,rd[maxn],zrj;
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
struct node {
  int v,nxt;
}e[8007];
const int inf=0x3f3f3f3f;
inline void ct(int u, int v) {
  e[++num].v=v;
  e[num].nxt=head[u];
  head[u]=num;
}
stack<int>q;
void tarjan(int u) {
  dfn[u]=low[u]=++cnt;
  q.push(u),vis[u]=1;
  for(int i=head[u];i;i=e[i].nxt) {
    int v=e[i].v;
    if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
    else if(vis[v]) low[u]=min(low[u],dfn[v]);
  }
  if(dfn[u]==low[u]) {
    int x=-1;js++;
    while(x!=u) {
      x=q.top();q.pop();
      bel[x]=js;
      block[js]=min(block[js],w[x]);
      vis[x]=0;
    }
  }
}
int main() {
  n=qread(),p=qread();
  memset(block,0x3f,sizeof(block));
  memset(w,0x3f,sizeof(w));
  for(int i=1,x,y;i<=p;++i) {
    x=qread(),y=qread();
    w[x]=y;
  }
  m=qread();
  for(int i=1,u,v;i<=m;++i) {
    u=qread(),v=qread();
    ct(u,v);
  }
  for(int i=1;i<=n;++i) if(!dfn[i]&&w[i]!=inf) tarjan(i);
  for(int i=1;i<=n;++i) {
    if(!dfn[i]) {
      printf("NO\n%d\n",i);
      return 0;
    }
  }
  for(int u=1;u<=n;++u) {
    for(int i=head[u];i;i=e[i].nxt) {
      int v=e[i].v;
      if(bel[u]!=bel[v]) rd[bel[v]]++;
    }
  }
  for(int i=1;i<=js;++i) if(!rd[i]) zrj+=block[i];
  printf("YES\n%d\n",zrj);
  return 0;
}

7. 洛谷P5159 WD與矩陣

題目背景

WD整日沉浸在矩陣中,無法自拔……

題目描述

WD特別喜歡矩陣,尤其是\(01\)矩陣。

一天,CX給了WD一個巨大的\(n\)\(m\)列的\(01\)矩陣,WD發現這個矩陣每行、每列的異或值都是\(0\).

CX隨後就問道:“WD,你知道有多少\(01\)矩陣每行每列異或值都是\(0\)嗎!?”WD當然不會這個問題,於是他來請教你。

由於答案可能很大,輸出結果模\(998244353\)的值即可。

輸入輸出格式

輸入格式:

第一行一個數\(T\),表示資料組數。

接下來\(T\)行每行兩個數\(n,m\),分別表示詢問的行數和列數。

輸出格式:

\(T\)行,每行一個數,表示答案\(mod\) \(998244353\)的結果。

輸入輸出樣例

輸入樣例#1:

2
2 2
2 2018

輸出樣例#1:

2
851481696

說明

\(subtask1(11pts):~1\le T\le 10,~1\le n,m\le 4\)

\(subtask1(43pts):~1\le T\le 5,~1\le n\le 5,~1\le m\le 1,000\)

\(subtask1(46pts):~1\le T\le 100,000,~1\le n,m\le 10^9\)

思路:

題意是讓你求滿足n行m列且每行每列異或值都是0的矩陣個數,因為是異或,所以只可能有兩個值,\(0\)\(1\),那麼每行可能的取值就是\(2^n\),然後最後值是0的情況是就是\(2^{n-1}\),然後擴充套件到列上,那麼就是\((2^{n-1})^{m-1}\),然後自己再打打表就發現,顯然這個式子是正確的,然後用快速冪求解,計算的過程中記得取模。

自己整理的題解

下面是我簡潔的程式碼:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
#define mod 998244353
using namespace std;
ll n,m;
int t;
inline ll fpow(ll a, ll b) {
  if(!b) return 1;
  ll ans=1;
  for(;b;b>>=1,a=(a*a)%mod)
    if(b&1) ans=(ans*a)%mod;
  return ans;
}
int main() {
  scanf("%d",&t);
  while(t--) {
    scanf("%lld%lld",&n,&m);
    printf("%lld\n",fpow(fpow(2,n-1),m-1));
  }
  return 0;
}

8. 洛谷P4113 [HEOI2012]採花

題目描述

蕭薰兒是古國的公主,平時的一大愛好是採花。

今天天氣晴朗,陽光明媚,公主清晨便去了皇宮中新建的花園採花。

花園足夠大,容納了n朵花,花有c種顏色(用整數1-c表示),且花是排成一排的,以便於公主採花。公主每次採花後會統計採到的花的顏色數,顏色數越多她會越高興!同時,她有一癖好,她不允許最後自己採到的花中,某一顏色的花只有一朵。為此,公主每採一朵花,要麼此前已採到此顏色的花,要麼有相當正確的直覺告訴她,她必能再次採到此顏色的花。

由於時間關係,公主只能走過花園連續的一段進行採花,便讓女僕福涵潔安排行程。福涵潔綜合各種因素擬定了m個行程,然後一一向你詢問公主能採到多少朵花(她知道你是程式設計高手,定能快速給出答案!),最後會選擇令公主最高興的行程(為了拿到更多獎金!)。

輸入輸出格式

輸入格式:

第一行四個空格隔開的整數n、c以及m。接下來一行n個空格隔開的整數,每個數在[1, c]間,第i個數表示第i朵花的顏色。接下來m行每行兩個空格隔開的整數l和r(l ≤ r),表示女僕安排的行程為公主經過第l到第r朵花進行採花。

輸出格式:

共m行,每行一個整數,第i個數表示公主在女僕的第i個行程中能採到的花的顏色數。

輸入輸出樣例

輸入樣例#1:

5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5

輸出樣例#1:

2
0
0
1
0

說明

對於\(100\%\)的資料,\(1 ≤ n ≤ 2*10^6\)\(c ≤ n,m ≤2*10^6\)

本題有兩個\(subtask\)

\(subtask1\)保證\(n,m,c \leq 3*10^5\),佔\(100\)

\(subtask2\)保證\(n,m,c \leq 2*10^6\),佔\(100\)

思路:這道題目跟HH項鍊有些相似,唯一的不同在於這道題目要除去區間內只出現過一次的顏色,那麼我們就可以利用一個\(nxt\)和一個\(last\)陣列來記錄一個每一種顏色的前驅和後繼狀態,即上次和下次出現的位置,沒有為\(0\),然後把詢問離線下來,按右端點排一遍序,其實左端點也行,無所謂的,然後用樹狀陣列維護區間和,然後定義一個指標,把\(m\)個詢問掃一遍,然後在這個過程中如果說上次的上次已經出現過了,那就把上次的上次那個位置\(-1\),上次的位置\(+1\),然後處理出區間和即可。時間複雜度\(nlogn\)

程式碼:

#include<cstdio>
#include<algorithm>
#include<cctype>
#define maxn 2000007
#define lb(x) x&(-x)
using namespace std;
int n,m,c,a[maxn],nxt[maxn],last[maxn],w[maxn];
inline int qread() {
  char c=getchar();int num=0,f=1;
  for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
  for(;isdigit(c);c=getchar()) num=num*10+c-'0';
  return num*f;
}
inline void add(int x, int w) {
  if(!x) return;
  while(x<=n) {
    a[x]+=w;
    x+=lb(x);
  }
}
inline int query(int x) {
  int ans=0;
  while(x) {
    ans+=a[x];
    x-=lb(x);
  }
  return ans;
}
struct node {
  int l,r,ans,id;
}e[maxn];
inline bool cmp1(node a, node b) {
  if(a.r!=b.r) return a.r<b.r;
  return a.l<b.l;
}
int main() {
  n=qread(),c=qread(),m=qread();
  for(int i=1;i<=n;++i) {
    w[i]=qread();
    nxt[i]=last[w[i]];
    last[w[i]]=i;
  }
  for(int i=1;i<=m;++i) e[i].l=qread(),e[i].r=qread(),e[i].id=i;
  sort(e+1,e+1+m,cmp1);
  int j=1;
  for(int i=1;i<=m;++i) {
    while(j<=e[i].r) add(nxt[nxt[j]],-1),add(nxt[j],1),++j;
    e[e[i].id].ans=query(e[i].r)-query(e[i].l-1);
  }
  for(int i=1;i<=m;++i) printf("%d\n",e[i].ans);
  return 0;
}