1. 程式人生 > >【題解】洛谷1600天天愛跑步(NOIP2016)

【題解】洛谷1600天天愛跑步(NOIP2016)

在這裡給大家提供一種非常簡單的方法。

我們先來轉化一下題面:題目要求我們求每一個點在某個時間能看到的人數,那我們也可以分別計算每一個人對於他跑步的那條路徑上的貢獻。

那麼我們可以發現一個顯而易見的事情:這條路經一定由一段上行和一段下行組成。而且有且只有這兩條路徑。所以我們分開來看這兩條路徑:

  1. 如果這條路徑是上行的,那麼我們設這個人跑了 TT 秒,起點深度為 depstdep_{st},則一定有 depnow=depstTdep_{now}=dep_{st}-Tnownow 表示當前第 TT 秒的位置)
  2. 同理,如果是下行的,那麼一定有 d
    epnow=depst+Tdep_{now}=dep_{st}+T

移項發現:depst=depnow+Tdep_{st}=dep_{now}+Tdepst=depnowTdep_{st}=dep_{now}-T

所以我們把每一個人的路徑都拆分成上行和下行兩段,然後分別按照 T+depnowT+dep_{now}depnowTdep_{now}-T 排序就行了。

既然我們現在處理出了每一段路徑的出發點的資訊,那麼我們可以把觀察者按照 depidep_i

排序,然後尋找那些能夠觀察到的點即可。

現在第二個問題又來了:怎麼對於麼一個觀察點,處理出所有資訊與它的 depdep 相等的點呢?

這個其實很好想,因為我們其實就是在求經過這個點的路徑數量,所以我們就可以在每條路徑的下端點做差分,然後統計每一個觀察點的字數和計可。(用dfsdfs序+樹狀陣列統計子樹和大家應該都會吧)最後記得統計完每一個資訊不同的觀察點後暴力將剛才插入的差分值刪除就行了。複雜度:O(nlogn)O(n·log n)

下面附上程式碼:

#include<queue>
#include<vector>
#include
<string.h>
#include<iostream> #include<algorithm> #define maxn 300005 using namespace std; struct nod{ int p,q,r; }a[maxn],b[maxn],c[2][maxn]; bool cmp(nod x,nod y){return x.q<y.q;} bool cmp2(nod x,nod y){return x.r<y.r;} int cnt0,cnt[2],n,m,s[maxn],e[maxn],ans[maxn],siz[maxn],dfn[maxn],dep[maxn],vis[maxn],t[maxn],sum[maxn],fa[maxn][21]; vector<int> edge[maxn]; queue<nod> q; int lowbit(int x){return x&(-x);} void ins(int x,int v){if(!x) return; for(int i=x;i<=n;i+=lowbit(i)) sum[i]+=v;} int ques(int x){int a=0; for(int i=x;i>0;i-=lowbit(i)) a+=sum[i]; return a;} void addedge(int x,int y){edge[x].push_back(y),edge[y].push_back(x);} void add(int x,int y,int k,int baze) {nod &now=c[k][++cnt[k]]; now.p=x,now.q=y,now.r=dep[k?y:x]-baze;} void dfs(int x) { for(int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; siz[x]=1,vis[x]=1,dfn[x]=++cnt0; for(int i=0;i<edge[x].size();i++) if(!vis[edge[x][i]]) fa[edge[x][i]][0]=x,dep[edge[x][i]]=dep[x]+1,dfs(edge[x][i]),siz[x]+=siz[edge[x][i]]; } int lca(int x,int y) { if(x==y) return x; if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } int main() { int x,y,g,now1=1,now2=1; cin>>n>>m; for(int i=1;i<n;i++) cin>>x>>y,addedge(x,y); for(int i=1;i<=n;i++) cin>>t[i]; sort(a+1,a+n+1,cmp); for(int i=1;i<=m;i++) cin>>s[i]>>e[i]; dep[1]=1,dfs(1); for(int i=1;i<=n;i++) a[i].q=t[i]+dep[i],b[i].q=dep[i]-t[i],a[i].p=b[i].p=i; sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp); for(int i=1;i<=m;i++) { int st=s[i],en=e[i];g=lca(st,en); if(g==st) add(en,st,1,0); else if(g==en) add(st,en,0,0); else { x=st; for(int i=20;i>=0;i--) if(dep[fa[x][i]]>dep[g]) x=fa[x][i]; add(st,x,0,0),add(en,g,1,dep[st]-dep[x]+1); } } sort(c[0]+1,c[0]+cnt[0]+1,cmp2),sort(c[1]+1,c[1]+cnt[1]+1,cmp2); for(int i=1;i<=n;i++) { while(a[i].q!=a[i-1].q && !q.empty()) {nod now=q.front();q.pop();ins(now.p,-1),ins(now.q,1);} for(;now1<=cnt[0] && c[0][now1].r<=a[i].q;now1++) if(a[i].q!=a[i-1].q && c[0][now1].r==a[i].q) ins(dfn[c[0][now1].p],1),ins(dfn[fa[c[0][now1].q][0]],-1),q.push((nod){dfn[c[0][now1].p],dfn[fa[c[0][now1].q][0]],0}); ans[a[i].p]+=ques(dfn[a[i].p]+siz[a[i].p]-1)-ques(dfn[a[i].p]-1); } while(!q.empty()) {nod now=q.front();q.pop();ins(now.p,-1),ins(now.q,1);} for(int i=1;i<=n;i++) { while(b[i].q!=b[i-1].q && !q.empty()) {nod now=q.front();q.pop();ins(now.p,-1),ins(now.q,1);} for(;now2<=cnt[1] && c[1][now2].r<=b[i].q;now2++) if(b[i].q!=b[i-1].q && c[1][now2].r==b[i].q) ins(dfn[c[1][now2].p],1),ins(dfn[fa[c[1][now2].q][0]],-1),q.push((nod){dfn[c[1][now2].p],dfn[fa[c[1][now2].q][0]],0}); ans[b[i].p]+=ques(dfn[b[i].p]+siz[b[i].p]-1)-ques(dfn[b[i].p]-1); } for(int i=1;i<=n;i

相關推薦

題解1600天天跑步NOIP2016

在這裡給大家提供一種非常簡單的方法。 我們先來轉化一下題面:題目要求我們求每一個點在某個時間能看到的人數,那我們也可以分別計算每一個人對於他跑步的那條路徑上的貢獻。 那麼我們可以發現一個顯而易見的事情:這條路經一定由一段上行和一段下行組成。而且有且只有這兩條路徑

P1600 天天跑步NOIp2016BZOJ4719

LCA 炒雞難的一題。。。碼量還不小。。。 只想到拆成鏈,然而不知道怎麼實現。。。只好認慫去看題解。。。 考慮每條路徑,拆成兩個鏈。一個從s到LCA,另一個從LCA到t。如果LCA有貢獻的話就把答案-1(不然就算重啦)。 然後在每個s打個1,在L

題解P1315 [NOIP2011TG] 觀光公交字首和+貪心

次元傳送門:洛谷P1315 思路 思路大概想到了 可是程式碼實現卻沒想到 所以參考題解了 D2T3的貪心果然有難度 我們考慮在每次用加速器有兩種情況 到下一個點還需要等待:以後的時間就不再影響了 到下一個點不需要等待:那麼就會影響到後面的時間直到出現情況1(或者到最後一個點) 用sum

題解P1169 [ZJOI2007] 棋盤製作座標DP+懸線法

次元傳送門:洛谷P1169 思路 浙江省選果然不一般 用到一個從來沒有聽過的演算法 懸線法: 所謂懸線法 就是用一條線(長度任意)在矩陣中判斷這條線能到達的最左邊和最右邊及這條線的長度 即可得到這個矩陣的最大值 那麼我們定義3個數組 l[i][j]表示(i,j)能到達最左邊的座標 r[i][j]

題解P1879 [USACO06NOV] Corn Fields狀壓DP

洛谷P1879:https://www.luogu.org/problemnew/show/P1879 思路 把題目翻譯成人話 在n*m的棋盤 每個格子不是0就是1 1表示可以種 0表示不能種 相鄰的格子不能同時種 求總方案數 把每行看成一個n位的2進位制數 預處理出每行的狀態後 進行DP

題解P1896 [SCOI2005] 互不侵犯狀壓DP

洛谷P1896:https://www.luogu.org/problemnew/show/P1896 前言 這是一道狀壓DP的經典題  原來已經做過了 但是快要NOIP 複習一波 關於一些位運算的知識點參考: https://blog.csdn.net/fox641941

題解P1351 聯合權值dfs、LCA

這道題一開始啥也沒想就用最短路寫,才40分,然後發現自己對尋找最大值取模了,改了之後60分。。然後又發現n個點,n-1條邊,其實這個圖就是一棵樹,每一個點到其餘點的最短路有且只有一條,完全可以用dfs對每個點進行擴充套件,擴充套件兩層找到點然後進行操作。。雖然看起來更簡便了,

題解P1975排序

space getchar() class std -- names char 圖書管理員 wap 分塊,註意重復的值之間的處理。跟普通分塊的操作一樣的啦,具體可以參見‘不勤勞的圖書管理員’。 #include <bits/stdc++.h> using na

題解P2418 yyy loves OI IV

轉移 || 暴力 兩種 clu OS AD spa IT 感覺很是妙啊……這題數次誤入歧途...最開始想的二維dp,單調隊列優化;無果,卒。於是沒忍住看了下標簽:暴力枚舉?搜索?於是開始想記憶化搜索。以為會有什麽很強的剪枝之類的;30分,卒。最後終於回到正道上:50 000

題解6月月賽 —— 「數學」約數個數和

分解 pri clas left pac 這樣的 DC 兩個 探測   看德國戰墨西哥去了結果發現比賽只剩下30分鐘……當然之後又思考這題挺久也還是不會做。看了一下題解,覺得這個做法挺厲害的,在這裏記錄一下:   原式實際上就是:(\(K +=

題解P1941 [NOIP2014TG] 飛揚的小鳥揹包DP

次元傳送門:洛谷P1941 思路 從題意可知 在每個單位時間內 可以無限地向上飛 但是隻能向下掉一次 所以我們可以考慮運用揹包解決這道題 上升時 用完全揹包 下降時 用01揹包 設f[x][y]為在座標(x,y)時的最小點選螢幕次數 當飛到天花板時和撞到柱子時特判 一開始設ans為極大值 如

題解P1373 小a和uim之大逃離座標DP

次元傳送門:洛谷P1373 思路 設f[i][j][t][1/0]表示走到(i,j)時 小a減去uim的差值為t 當前是小a取(0) uim取(1) 那麼轉移就很明顯了 f[i][j][t][0]=(f[i][j][t][0]+f[i-1][j][(t-map[i][j]+k)%k][1])%1

題解P1273 有線電視網樹上分組揹包

次元傳送門:洛谷P1273 思路 一開始想的是普通樹形DP 但是好像實現不大好 觀摩了一下題解 是樹上分組揹包 設f[i][j]為以i為根的子樹中取j個客戶得到的總價值 我們可以以i為根有j組 在每一組中分別又取1個,2個,3個......n個客戶 化為揹包思想即 j為一共有j組 揹包容量為每

題解 1120 小木棍 [資料加強版]

原題 剪枝好題,可以有以下9個剪枝(基本上都是可行性剪枝,還有一些搜尋順序的剪枝),這是一道除了生日蛋糕以外的剪枝好題當然不會告訴你Biscuit46花了1h做這道題目 #include<stdio.h> #include<stdlib.h> #include<strin

題解P2577 [ZJOI2005] 午餐DP+貪心

次元傳送門:洛谷P2577  思路 首先貪心是必須的 我們能感性地理解出吃飯慢的必須先吃飯(結合一下生活) 因此我們可以先按吃飯時間從大到小排序 然後就能自然地想到用f[i][j][k]表示前i個人在第一個視窗排隊用了j時間 在第二個視窗排隊用了k時間 然後就自然地炸空間了 所以我們要

題解P1070 道路遊戲線性DP

次元傳送門:洛谷P1070 思路 一開始以為要用什麼玄學優化 沒想到O3就可以過了 我們只需要設f[i]為到時間i時的最多金幣 需要倒著推回去 即當前值可以從某個點來 那麼狀態轉移方程為: f[i]=max(f[i],f[i-k]+val-cost[now]); now表示從now這個

題解P3959 [NOIP2017TG] 寶藏狀壓DP+DFS

洛谷P3959:https://www.luogu.org/problemnew/show/P3959 前言 NOIP2017時還很弱(現在也很弱 看出來是DP 但是並不會狀壓DP 現在看來思路並不複雜 只是存狀態有點難想到 思路 因為n最大為12 所以可以想到是狀壓  

題解P2375 [NOI2014] 動物園KMP

洛谷P2375:https://www.luogu.org/problemnew/show/P2375 思路 這道題可以說是完全重新整理了本蒟蒻對KMP的理解 感覺對next陣列的理解上升到一個新的高度 首先題目給出了next陣列的定義 關於整個字串 next[len]為字首與字尾相同的最

題解P3865 ST表

題目連結 #include<cstdio> #include<algorithm> #include<cmath> using namespace std; const int N=1e5+10; inline int read() { i

題解P2678 跳石頭(二分)

1-1e9二分答案,設其為最短距離。判斷如果該距離可以就向上二分(單調遞增,求最大值),注意判斷mid+1可以避免邊界問題。 判斷函式統計移走石頭個數,如果相鄰石頭之間距離比要的最短距離還小就移走。移走石頭個數小於M就返回true #include<cstdio&g