前言

关于\(NOIP2018\),详见此博客:NOIP2018学军中学游记(11.09~11.11)

\(Day2\)的题目和\(Day1\)比起来,真的是难了很多啊。

\(T1\):旅行(点此看题面

对于树的情况,显然可以把相邻的点全部存下来,排序一遍后依次遍历即可。

对于基环外向树的情况,一种简单的方法是每次断一条边,把它当成树的情况,这样是\(O(n^2)\)的。

但我考场上没想到这种做法,结果对于环上的情况单独讨论,结果把这题弄成了一个极为复杂的模拟题,总共打了两个小时才打完。不过我这样的做法貌似是\(O(n)\)的(如果不算\(sort\)的\(log\))。

具体是怎么做的就不讲了(比较麻烦),有兴趣可以看一下代码(要看的话最好画图理解一下)。

我个人还是比较推荐用删边的做法的。

\(P.S.\)貌似有一个加强版的题目:【洛谷5049】旅行(数据加强版),我这种做法是能过的。

代码如下:

  1. #include<bits/stdc++.h>
  2. #define N 5000
  3. #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
  4. #define swap(x,y) (x^=y^=x^=y)
  5. using namespace std;
  6. int n,m,ee,cur,lnk[N+5],vis[N+5],data[N+5];
  7. struct edge
  8. {
  9. int to,nxt;
  10. }e[(N<<1)+5];
  11. class Class_TreeSolver//对于树的情况
  12. {
  13. public:
  14. inline void Solve(int x=1,int lst=0)
  15. {
  16. register int i,H=cur+1,T;
  17. for(printf("%d ",x),i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(data[++cur]=e[i].to);
  18. for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i) Solve(data[i],x);
  19. }
  20. }TreeSolver;
  21. inline void Begin_Circle(int,int);
  22. class Class_CircleTreeSolver//对于基环外向树的情况
  23. {
  24. private:
  25. int Top,used[N+5],StackH[N+5],StackT[N+5],StackPos[N+5];
  26. class Class_CircleChecker//判断一个节点是否在环中
  27. {
  28. private:
  29. int Found,fa[N+5],In[N+5],vis[N+5],Depth[N+5];
  30. public:
  31. Class_CircleChecker() {Found=0;}
  32. inline void Init(int x=1,int lst=0)//初始化
  33. {
  34. register int i,y;
  35. for(vis[x]=1,i=lnk[x];i;i=e[i].nxt)
  36. {
  37. if(!(e[i].to^lst)) continue;
  38. if(vis[y=e[i].to])
  39. {
  40. if(Depth[x]<Depth[y]) swap(x,y);
  41. while(Depth[x]>Depth[y]) In[x]=1,x=fa[x];
  42. while(x^y) In[x]=In[y]=1,x=fa[x],y=fa[y];
  43. return (void)(In[x]=Found=1);
  44. }
  45. if(Depth[e[i].to]=Depth[fa[e[i].to]=x]+1,Init(e[i].to,x),Found) return;
  46. }
  47. vis[x]=0;
  48. }
  49. inline bool InCircle(int x) {return In[x];}//判断x是否在环中
  50. }C;
  51. inline void dfs(int x,int lst)//普通的dfs
  52. {
  53. if(used[x]) return;
  54. if(C.InCircle(x)) return Begin_Circle(x,lst);//如果当前节点在环上,特殊处理
  55. register int i,H=cur+1,T;
  56. for(used[x]=1,printf("%d ",x),i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&(data[++cur]=e[i].to);
  57. for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i) dfs(data[i],x);
  58. }
  59. inline void dfs_Circle(int x,int lst,int Begin,int OtherSide)//在环上dfs
  60. {
  61. if(used[x]) return;//如果访问过当前节点,退出
  62. register int i,H=cur+1,T;
  63. for(used[x]=1,printf("%d ",x),i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&(data[++cur]=e[i].to);
  64. for(T=cur,sort(data+H,data+T+1),i=H;i<T;++i)//注意这里写的是小于T
  65. {
  66. if(C.InCircle(data[i])) StackH[++Top]=i+1,StackT[Top]=T,StackPos[Top]=x,dfs_Circle(data[i],x,Begin,OtherSide),--Top;//如果依然是环上的节点,用栈存下当前节点信息,继续dfs
  67. else dfs(data[i],x);//否则换成普通的dfs
  68. }
  69. if(C.InCircle(data[T]))//如果最后一个元素在环上
  70. {
  71. if(OtherSide&&data[StackH[Top]]<data[i]&&!used[OtherSide])//如果走这个节点没有回到这个环的另一边更优
  72. {
  73. while(Top>1)//如果栈中元素个数大于1
  74. {
  75. for(i=StackH[Top];i<=StackT[Top];++i) dfs(data[i],StackPos[Top]);//处理当前栈顶元素可到达的剩余节点
  76. --Top;//弹出
  77. }
  78. while(StackH[1]<=StackT[1]&&data[StackH[1]]<OtherSide) dfs(data[StackH[1]++],Begin);//特殊处理栈中的最后一个元素
  79. dfs_Circle(OtherSide,x,Begin,0);//搜索另一端
  80. }
  81. else dfs_Circle(data[i],x,Begin,OtherSide);//如果不是更优,依然走当前节点
  82. }
  83. else dfs(data[i],x);//如果不在环上,就用普通的dfs
  84. }
  85. inline void Begin_Circle(int x,int lst)//开始处理环的情况
  86. {
  87. register int i,H=cur+1,T,s1=0,s2=0;
  88. for(used[x]=1,printf("%d ",x),i=lnk[x];i;i=e[i].nxt) if(!used[e[i].to]&&C.InCircle(data[++cur]=e[i].to)) s1?s2=e[i].to:s1=e[i].to;//用s1和s2存储当前点在环上的两个邻点
  89. for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i)
  90. {
  91. if(C.InCircle(data[i])) StackH[++Top]=i+1,StackT[Top]=T,StackPos[Top]=x,dfs_Circle(data[i],x,x,data[i]^s1?s1:s2);//对于在环上的节点特殊处理
  92. else dfs(data[i],x);//否则使用普通的dfs
  93. }
  94. }
  95. public:
  96. inline void Solve() {C.Init(),used[0]=1,dfs(1,0);}
  97. }CircleTreeSolver;
  98. int main()
  99. {
  100. register int i,j,k,x,y;
  101. for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
  102. if(m==n-1) TreeSolver.Solve();else CircleTreeSolver.Solve();
  103. return 0;
  104. }

\(T2\):填数游戏(点此看题面

如果用\(ans_{x,y}\)表示\(n=x,m=y\)时的答案,则我们要知道两个性质:

  1. 对于任意\(n,m\),满足\(ans_{n,m}=ans_{m,n}\)。(显然)
  2. 对于任意\(m>n+1\),满足\(ans_{n,m}=ans_{n,n+1}*3^{m-n-1}\)。(我也不会证)

则不难发现,我们只需求出\(ans_{i,i}\)和\(ans_{i,i+1}(1\le i\le8)\),就可以推出全部答案。

据说\(ans_{i,i}\)与\(ans_{i-1,i-1}\)、\(ans_{i,i+1}\)与\(ans_{i,i}\)之间是有一定数量关系的,然而我并没有找到规律。

但是没关系,我们还可以打表啊!

不难发现,对于每一条从左下向右上的斜线中,必然可以被分成两部分,其中前一部分全为\(0\),后一部分全为\(1\)(或整条线全为\(0\)或\(1\))。

那么,我们就可以枚举每条斜线中选择多少个\(1\),然后对其进行\(O(nm(n+m))\)验证,就可以在较快的时间内打完表了(实际上我打了一个下午)。

代码如下:

  1. #include<bits/stdc++.h>
  2. #define MOD 1000000007
  3. #define N 8
  4. #define swap(x,y) (x^=y^=x^=y)
  5. #define GetID(x,y) ((x)-2<<1|(y))
  6. using namespace std;
  7. const int List[2*N]={12,36,112,336,912,2688,7136,21312,56768,170112,453504,1360128,3626752,10879488};//最后打出来的表(我将二维压成了一维)
  8. int n,m;
  9. class Class_ListMaker//打表代码
  10. {
  11. private:
  12. #define ListSize 8
  13. int n,m,ans,num[N+5][N+5];
  14. inline bool IsLegal(int x,int y)//验证当前位置合法性
  15. {
  16. register int x1=x,y1=y+1,x2=x+1,y2=y;
  17. while(x1+y1<=n+m)
  18. {
  19. if(num[x1][y1]^num[x2][y2]) return num[x1][y1]<num[x2][y2];
  20. (x1^n?++x1:++y1),(y2^m?++y2:++x2);
  21. }
  22. return true;
  23. }
  24. inline bool check() {for(register int i=1,j;i<n;++i) for(j=1;j<m;++j) if(!IsLegal(i,j)) return false;return true;}//枚举每一个位置
  25. inline void Solve(int s=2)//枚举所有情况
  26. {
  27. register int i;
  28. if(s>n+m) return (void)(!((ans+=check())^MOD)&&(ans=0));
  29. for(Solve(s+1),i=n;i;--i) s-i>=1&&s-i<=m&&(num[i][s-i]=1,Solve(s+1),0);
  30. for(i=n;i;--i) s-i>=1&&s-i<=m&&(num[i][s-i]=0);
  31. }
  32. public:
  33. inline void MakeList()
  34. {
  35. freopen("List.txt","w",stdout);
  36. for(cout<<"const int List[2*N]={",n=2;n<=ListSize;++n) m=n,ans=0,Solve(),printf("%d,",ans),m=n+1,ans=0,Solve(),printf("%d%c",ans,n^ListSize?',':'}');//输出表
  37. putchar(';'),exit(0);
  38. }
  39. }M;
  40. inline int quick_pow(int x,int y)
  41. {
  42. register int res=1;
  43. while(y) (y&1)&&(res=1LL*res*x%MOD),x=1LL*x*x%MOD,y>>=1;
  44. return res;
  45. }
  46. int main()
  47. {
  48. // M.MakeList();
  49. freopen("game.in","r",stdin),freopen("game.out","w",stdout);
  50. scanf("%d%d",&n,&m),n>m&&swap(n,m),printf("%d",n^1?(m<=n+1?List[GetID(n,m-n)]:1LL*List[GetID(n,1)]*quick_pow(3,m-n-1)%MOD):quick_pow(2,m));//依据规律输出答案
  51. return 0;
  52. }

\(T3\):保卫王国(点此看题面

这题的正解是动态DP,不会动态DP的可以先去做这道黑色的模板题

好吧,实际上这题是可以用倍增+\(DP\)来解决的。

考虑用\(In_{x,0/1}\)来表示不选/选\(x\)节点时在\(x\)子树内达成条件的最小代价(为方便起见,我们再用一个\(In_{x,2}\)来表示\(min(In_{x,0},In_{x,1})\)),并用\(Out_{x,0/1}\)来表示不选/选\(x\)节点时在\(x\)子树外达成条件的最小代价

这两个数组可以通过两遍\(dfs\)来预处理。

然后,我们还要预处理出一个数组\(f_{x,y,sx(0/1),sy(0/1)}\)来表示当\(x\)不选/选,\(fa_{x,y}\)不选/选时,使属于以\(fa_{x,y}\)为根子树而不属于以\(x\)为根子树的所有节点达成条件的最小代价

这个数组可以在预处理\(fa\)数组时一起预处理掉。

只要枚举\(fa_{i,j-1}\)选与不选就可以进行转移了。

而对于询问,我们就采用倍增\(LCA\)的思想向上跳。

如果两个节点为祖先关系,则直接向上跳,一边跳一边用一个数组\(g_{0/1}\)记录在当前节点不选与选时使当前子树满足条件分别需要的最小代价,最后答案就是\(g_{sx}+Out_{y,sy}\)。

而不为祖先关系的情况也是同理的,分别将两个节点跳到\(LCA_{x,y}\)的左右子节点,然后枚举\(LCA_{x,y}\)选与不选,然后减去之前预处理时得到的答案,加上新求出的最小代价,即可求出答案。

具体实现见代码:

  1. #include<bits/stdc++.h>
  2. #define N 100000
  3. #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
  4. #define swap(x,y) (x^=y^=x^=y)
  5. #define min(x,y) ((x)<(y)?(x):(y))
  6. #define Gmin(x,y) (x>(y)&&(x=(y)))
  7. #define LL long long
  8. #define INF 1e18
  9. using namespace std;
  10. int n,ee,lnk[N+5],Cost[N+5];
  11. struct edge
  12. {
  13. int to,nxt;
  14. }e[(N<<1)+5];
  15. class Class_FIO
  16. {
  17. private:
  18. #define Fsize 100000
  19. #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
  20. #define pc(ch) (void)(FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
  21. int Top,FoutSize;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
  22. public:
  23. Class_FIO() {A=B=Fin;}
  24. inline void read(int &x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
  25. inline void readc(char &x) {while(isspace(x=tc()));}
  26. inline void writeln(LL x) {if(!x) return pc('0'),pc('\n');x<0&&(pc('-'),x=-x);while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);pc('\n');}
  27. inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
  28. }F;
  29. class Class_TreeDP
  30. {
  31. private:
  32. #define LogN 17
  33. #define jump(x,y,p) (g_now[p][0]=min(g_lst[p][0]+f[x][y][0][0],g_lst[p][1]+f[x][y][1][0]),g_now[p][1]=min(g_lst[p][0]+f[x][y][0][1],g_lst[p][1]+f[x][y][1][1]),x=fa[x][y],g_lst[p][0]=g_now[p][0],g_lst[p][1]=g_now[p][1])
  34. int Depth[N+5],fa[N+5][LogN+5];LL In[N+5][3],Out[N+5][2],f[N+5][LogN+5][2][2],g_now[2][2],g_lst[2][2];
  35. inline void dfs1(int x)//第一遍dfs,预处理出Depth,In数组
  36. {
  37. register int i;
  38. for(i=lnk[x],In[x][0]=0,In[x][1]=Cost[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&(Depth[e[i].to]=Depth[fa[e[i].to][0]=x]+1,dfs1(e[i].to),In[x][0]+=In[e[i].to][1],In[x][1]+=In[e[i].to][2]);
  39. In[x][2]=min(In[x][0],In[x][1]);
  40. }
  41. inline void dfs2(int x)//第二遍dfs,预处理出Out,fa,f数组
  42. {
  43. register int i;
  44. f[x][0][0][0]=INF,f[x][0][1][0]=In[fa[x][0]][0]-In[x][1],f[x][0][0][1]=f[x][0][1][1]=In[fa[x][0]][1]-In[x][2];
  45. for(i=1;i<=LogN;++i)
  46. {
  47. fa[x][i]=fa[fa[x][i-1]][i-1],
  48. //枚举中间转移点选与不选即可进行转移
  49. f[x][i][0][0]=min(f[x][i-1][0][0]+f[fa[x][i-1]][i-1][0][0],f[x][i-1][0][1]+f[fa[x][i-1]][i-1][1][0]),
  50. f[x][i][0][1]=min(f[x][i-1][0][0]+f[fa[x][i-1]][i-1][0][1],f[x][i-1][0][1]+f[fa[x][i-1]][i-1][1][1]),
  51. f[x][i][1][0]=min(f[x][i-1][1][0]+f[fa[x][i-1]][i-1][0][0],f[x][i-1][1][1]+f[fa[x][i-1]][i-1][1][0]),
  52. f[x][i][1][1]=min(f[x][i-1][1][0]+f[fa[x][i-1]][i-1][0][1],f[x][i-1][1][1]+f[fa[x][i-1]][i-1][1][1]);
  53. }
  54. for(i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&(Out[e[i].to][0]=Out[e[i].to][1]=Out[x][1]+In[x][1]-In[e[i].to][2],Gmin(Out[e[i].to][1],Out[x][0]+In[x][0]-In[e[i].to][1]),dfs2(e[i].to),0);
  55. }
  56. public:
  57. inline void Init() {dfs1(Depth[1]=1),dfs2(1);}
  58. inline bool Identify(int x,int y) {return !(fa[x][0]^y&&fa[y][0]^x);}//判断两节点是否相邻
  59. inline LL GetAns(int x,int sx,int y,int sy)//采用倍增LCA的思想,倍增求解答案
  60. {
  61. register int i;
  62. Depth[x]<Depth[y]&&(swap(x,y),swap(sx,sy)),g_lst[0][sx]=In[x][sx],g_lst[1][sy]=In[y][sy],g_lst[0][sx^1]=g_lst[1][sy^1]=INF;
  63. for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]^Depth[y])&(1<<i)) jump(x,i,0);
  64. if(!(x^y)) return g_now[0][sy]+Out[y][sy];
  65. for(i=LogN;~i;--i) if(fa[x][i]^fa[y][i]) jump(x,i,0),jump(y,i,1);
  66. return min(In[fa[x][0]][0]+Out[fa[x][0]][0]-In[x][1]-In[y][1]+g_lst[0][1]+g_lst[1][1],In[fa[x][0]][1]+Out[fa[x][0]][1]-In[x][2]-In[y][2]+min(g_lst[0][0],g_lst[0][1])+min(g_lst[1][0],g_lst[1][1]));//返回答案
  67. }
  68. }T;
  69. int main()
  70. {
  71. register int i,Q,x,y,sx,sy,TypeY;register char TypeX;
  72. for(F.read(n),F.read(Q),F.readc(TypeX),F.read(TypeY),i=1;i<=n;++i) F.read(Cost[i]);
  73. for(i=1;i<n;++i) F.read(x),F.read(y),add(x,y),add(y,x);
  74. for(T.Init();Q;--Q) F.read(x),F.read(sx),F.read(y),F.read(sy),F.writeln(!sx&&!sy&&T.Identify(x,y)?-1:T.GetAns(x,sx,y,sy));
  75. return F.clear(),0;
  76. }

NOIP2018提高组Day2 解题报告的更多相关文章

  1. 【未完成0.0】Noip2012提高组day2 解题报告

    第一次写一套题的解题报告,感觉会比较长.(更新中Loading....):) 题目: 第一题:同余方程 描述 求关于x的同余方程ax ≡ 1 (mod b)的最小正整数解. 格式 输入格式 输入只有一 ...

  2. noip2015提高组day2解题报告

    1.跳石头 题目描述 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 N 块岩石( ...

  3. NOIP2018提高组Day1 解题报告

    前言 关于\(NOIP2018\),详见此博客:NOIP2018学军中学游记(11.09~11.11). 这次\(NOIP\ Day1\)的题目听说很简单(毕竟是三道原题),然而我\(T3\)依然悲剧 ...

  4. 【NOIP2015】提高组D1 解题报告

    P1978神奇的幻方 Accepted 描述 幻方是一种很神奇的 N ∗ N 矩阵:它由数字 1,2,3, … … , N ∗ N 构成,且每行.每列及两条对角线上的数字之和都相同. 当 N 为奇数时 ...

  5. NOIP2018普及组初赛解题报告

    本蒟蒻参加了今年的NOIP2018普及组的初赛 感觉要凉 总而言之,今年的题要说完全没有难度倒也不至于,还有不少拼RP的题,比如第一次问题求解考逻辑推理,第一次完善程序考双链表等 下面我就和大家一起看 ...

  6. HGOI20180813 (NOIP2018 提高组 Day2 模拟试题)

    省常中省选提高Day2 继续 第一题就考了贪心,正解95pts的贪心策略第一印象是想到的,但是被自己否定掉了qwq,然后打了 不是正解的贪心,样例5没过(可怜)思路如下:先找出每个门对应可以通过的人数 ...

  7. NOIP 2018 提高组初赛解题报告

    单项选择题: D 进制转换题,送分: D 计算机常识题,Python是解释运行的: B 常识题,1984年小平爷爷曰:“娃娃抓起”: A 数据结构常识题,带进去两个数据就可以选出来: D 历年真题没有 ...

  8. 牛客NOIP暑期七天营-提高组1 解题报告

    https://ac.nowcoder.com/acm/contest/920#question A 构造+双指针 发现m的限制是1e5,而点数是5e4,所以不能构造太多的边,思考一下最短路树的定义. ...

  9. GX/GZOI2019 day2 解题报告

    GX/GZOI2019 day2 解题报告 题目链接 逼死强迫症 旅行者 旧词 t1 逼死强迫症 显然地,记 \(f(i)\) 为长度为 \(i\) 的木板的答案,可得: \(\\\) \[f(i)= ...

随机推荐

  1. THE WAY TO HACKER

    1/编程篇88课时(预计三个月) 此阶段主要侧重于培养学员发现问题的能力,并对各大平台各个操作系统有一个整体性认知,迅速建立起较高的计算机素养,并形成对于信息安全核心思想的初步探索及认知,为后续专项课 ...

  2. QQ音乐MP3下载

    QQ音乐MP3下载 没错本次写的内容的对象是我们熟知的QQ Music. 本篇文章涉及内容包括:Python,爬虫,json解析,request 库的使用 缘起 前几天刷B站无意中又刷到了一首神曲,“ ...

  3. Linux调优(内存,CPU)

    一.相关概念简介 system call:系统调用 time slice:cpu时间片 O(1):Linux系统进程调度器 page frame:分页 RSS:常驻内存集,无法被页面化的数据 MMU: ...

  4. Web攻击技术---OWASP top

    整理OWASP top 10 部分内容,方便日后查看.想深入了解的,请移步参考中的网站. OWASP Top 10 注入 将不受信任的数据作为命令或者查询的一部分发送到解析器时,会发生诸如SQL注入. ...

  5. 7、kvm迁移操作

    虚拟机迁移要确保虚拟机是关机状态. virsh shutdown privi-server virsh dumpxml privi-server > /etc/libvirt/qemu/priv ...

  6. 5、kvm快照相关操作

    kvm虚拟机默认使用raw格式的镜像格式,性能最好,速度最快,不支持支持镜像,zlib磁盘压缩,AES加密等.要使用镜像功能,但是磁盘格式为qcow2就是支持. 关闭虚拟机 virsh shutdow ...

  7. 同域内的两台电脑,一台访问另一台上搭建的IIS站点无法访问解决方法

    需要在搭建IIS站点的机器上,打开[高级安全Windows防火墙],新建[入站规则],添加外部允许访问的端口号即可.

  8. python_sting字符串的方法及注释

    string类型是python内置的类型,无需安装   方法/属性 说明   capitalize()   把字符串的第一个字符改为大写   casefold()   把整个字符串的所有字符改为小写 ...

  9. Jmeter4.0---- HTTP请求默认值(15)

    1.说明 在线程组中,添加”HTTP请求默认值”,并填写 协议,服务器名称/IP ,端口号,编码等 ,之后该线程组中的所有请求,可以不用填写这几项,启动后会直接调用”HTTP请求默认值”中的数据,方便 ...

  10. Jmeter4.0----HTTP Cookie管理器_抓取cookie中的参数(13)

    1.说明 请求结束后,要通过登录用户的JSESSIONID判断用户是否登录成功 2.步骤 第一步:添加 HTTP Cookie管理器 录制前,创建”线程组”,线程组=>配置元件=>HTTP ...