前言

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

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

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

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

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

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

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

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

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

代码如下:

#include<bits/stdc++.h>
#define N 5000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m,ee,cur,lnk[N+5],vis[N+5],data[N+5];
struct edge
{
int to,nxt;
}e[(N<<1)+5];
class Class_TreeSolver//对于树的情况
{
public:
inline void Solve(int x=1,int lst=0)
{
register int i,H=cur+1,T;
for(printf("%d ",x),i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(data[++cur]=e[i].to);
for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i) Solve(data[i],x);
}
}TreeSolver;
inline void Begin_Circle(int,int);
class Class_CircleTreeSolver//对于基环外向树的情况
{
private:
int Top,used[N+5],StackH[N+5],StackT[N+5],StackPos[N+5];
class Class_CircleChecker//判断一个节点是否在环中
{
private:
int Found,fa[N+5],In[N+5],vis[N+5],Depth[N+5];
public:
Class_CircleChecker() {Found=0;}
inline void Init(int x=1,int lst=0)//初始化
{
register int i,y;
for(vis[x]=1,i=lnk[x];i;i=e[i].nxt)
{
if(!(e[i].to^lst)) continue;
if(vis[y=e[i].to])
{
if(Depth[x]<Depth[y]) swap(x,y);
while(Depth[x]>Depth[y]) In[x]=1,x=fa[x];
while(x^y) In[x]=In[y]=1,x=fa[x],y=fa[y];
return (void)(In[x]=Found=1);
}
if(Depth[e[i].to]=Depth[fa[e[i].to]=x]+1,Init(e[i].to,x),Found) return;
}
vis[x]=0;
}
inline bool InCircle(int x) {return In[x];}//判断x是否在环中
}C;
inline void dfs(int x,int lst)//普通的dfs
{
if(used[x]) return;
if(C.InCircle(x)) return Begin_Circle(x,lst);//如果当前节点在环上,特殊处理
register int i,H=cur+1,T;
for(used[x]=1,printf("%d ",x),i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&(data[++cur]=e[i].to);
for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i) dfs(data[i],x);
}
inline void dfs_Circle(int x,int lst,int Begin,int OtherSide)//在环上dfs
{
if(used[x]) return;//如果访问过当前节点,退出
register int i,H=cur+1,T;
for(used[x]=1,printf("%d ",x),i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&(data[++cur]=e[i].to);
for(T=cur,sort(data+H,data+T+1),i=H;i<T;++i)//注意这里写的是小于T
{
if(C.InCircle(data[i])) StackH[++Top]=i+1,StackT[Top]=T,StackPos[Top]=x,dfs_Circle(data[i],x,Begin,OtherSide),--Top;//如果依然是环上的节点,用栈存下当前节点信息,继续dfs
else dfs(data[i],x);//否则换成普通的dfs
}
if(C.InCircle(data[T]))//如果最后一个元素在环上
{
if(OtherSide&&data[StackH[Top]]<data[i]&&!used[OtherSide])//如果走这个节点没有回到这个环的另一边更优
{
while(Top>1)//如果栈中元素个数大于1
{
for(i=StackH[Top];i<=StackT[Top];++i) dfs(data[i],StackPos[Top]);//处理当前栈顶元素可到达的剩余节点
--Top;//弹出
}
while(StackH[1]<=StackT[1]&&data[StackH[1]]<OtherSide) dfs(data[StackH[1]++],Begin);//特殊处理栈中的最后一个元素
dfs_Circle(OtherSide,x,Begin,0);//搜索另一端
}
else dfs_Circle(data[i],x,Begin,OtherSide);//如果不是更优,依然走当前节点
}
else dfs(data[i],x);//如果不在环上,就用普通的dfs
}
inline void Begin_Circle(int x,int lst)//开始处理环的情况
{
register int i,H=cur+1,T,s1=0,s2=0;
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存储当前点在环上的两个邻点
for(T=cur,sort(data+H,data+T+1),i=H;i<=T;++i)
{
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);//对于在环上的节点特殊处理
else dfs(data[i],x);//否则使用普通的dfs
}
}
public:
inline void Solve() {C.Init(),used[0]=1,dfs(1,0);}
}CircleTreeSolver;
int main()
{
register int i,j,k,x,y;
for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
if(m==n-1) TreeSolver.Solve();else CircleTreeSolver.Solve();
return 0;
}

\(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))\)验证,就可以在较快的时间内打完表了(实际上我打了一个下午)。

代码如下:

#include<bits/stdc++.h>
#define MOD 1000000007
#define N 8
#define swap(x,y) (x^=y^=x^=y)
#define GetID(x,y) ((x)-2<<1|(y))
using namespace std;
const int List[2*N]={12,36,112,336,912,2688,7136,21312,56768,170112,453504,1360128,3626752,10879488};//最后打出来的表(我将二维压成了一维)
int n,m;
class Class_ListMaker//打表代码
{
private:
#define ListSize 8
int n,m,ans,num[N+5][N+5];
inline bool IsLegal(int x,int y)//验证当前位置合法性
{
register int x1=x,y1=y+1,x2=x+1,y2=y;
while(x1+y1<=n+m)
{
if(num[x1][y1]^num[x2][y2]) return num[x1][y1]<num[x2][y2];
(x1^n?++x1:++y1),(y2^m?++y2:++x2);
}
return true;
}
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;}//枚举每一个位置
inline void Solve(int s=2)//枚举所有情况
{
register int i;
if(s>n+m) return (void)(!((ans+=check())^MOD)&&(ans=0));
for(Solve(s+1),i=n;i;--i) s-i>=1&&s-i<=m&&(num[i][s-i]=1,Solve(s+1),0);
for(i=n;i;--i) s-i>=1&&s-i<=m&&(num[i][s-i]=0);
}
public:
inline void MakeList()
{
freopen("List.txt","w",stdout);
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?',':'}');//输出表
putchar(';'),exit(0);
}
}M;
inline int quick_pow(int x,int y)
{
register int res=1;
while(y) (y&1)&&(res=1LL*res*x%MOD),x=1LL*x*x%MOD,y>>=1;
return res;
}
int main()
{
// M.MakeList();
freopen("game.in","r",stdin),freopen("game.out","w",stdout);
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));//依据规律输出答案
return 0;
}

\(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}\)选与不选,然后减去之前预处理时得到的答案,加上新求出的最小代价,即可求出答案。

具体实现见代码:

#include<bits/stdc++.h>
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
#define min(x,y) ((x)<(y)?(x):(y))
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define LL long long
#define INF 1e18
using namespace std;
int n,ee,lnk[N+5],Cost[N+5];
struct edge
{
int to,nxt;
}e[(N<<1)+5];
class Class_FIO
{
private:
#define Fsize 100000
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
#define pc(ch) (void)(FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
int Top,FoutSize;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
public:
Class_FIO() {A=B=Fin;}
inline void read(int &x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
inline void readc(char &x) {while(isspace(x=tc()));}
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');}
inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
}F;
class Class_TreeDP
{
private:
#define LogN 17
#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])
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];
inline void dfs1(int x)//第一遍dfs,预处理出Depth,In数组
{
register int i;
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]);
In[x][2]=min(In[x][0],In[x][1]);
}
inline void dfs2(int x)//第二遍dfs,预处理出Out,fa,f数组
{
register int i;
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];
for(i=1;i<=LogN;++i)
{
fa[x][i]=fa[fa[x][i-1]][i-1],
//枚举中间转移点选与不选即可进行转移
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]),
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]),
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]),
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]);
}
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);
}
public:
inline void Init() {dfs1(Depth[1]=1),dfs2(1);}
inline bool Identify(int x,int y) {return !(fa[x][0]^y&&fa[y][0]^x);}//判断两节点是否相邻
inline LL GetAns(int x,int sx,int y,int sy)//采用倍增LCA的思想,倍增求解答案
{
register int i;
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;
for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]^Depth[y])&(1<<i)) jump(x,i,0);
if(!(x^y)) return g_now[0][sy]+Out[y][sy];
for(i=LogN;~i;--i) if(fa[x][i]^fa[y][i]) jump(x,i,0),jump(y,i,1);
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]));//返回答案
}
}T;
int main()
{
register int i,Q,x,y,sx,sy,TypeY;register char TypeX;
for(F.read(n),F.read(Q),F.readc(TypeX),F.read(TypeY),i=1;i<=n;++i) F.read(Cost[i]);
for(i=1;i<n;++i) F.read(x),F.read(y),add(x,y),add(y,x);
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));
return F.clear(),0;
}

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. React.Component(V16.8.6)

    组件的生命周期 挂载 当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下: constructor() static getDerivedStateFromProps() render() ...

  2. kylin cube 构建过程

    本文是对 http://kylin.apache.org/docs20/howto/howto_optimize_build.html的翻译,以便阅读. 1.  创建 Hive 中间表(Create ...

  3. C#之抽象类、虚方法、重写、接口、密封类

    前言    学了这么长时间的C#,我想说对于这个东东还是不是特别了解它,以至于让我频频郁闷.每次敲代码的时候都没有一种随心所欲的感觉.所以不得不在网上搜集一些资料,look 了 look~ 内容   ...

  4. 2017-10-5 清北刷题冲刺班p.m

    套路(拓扑排序) /* 对每个联通块单独考虑. 每个联通块是一个环套树,树边拎出来可以随意定向,记树边为 m,所以树的方案数为2^m . 对于环来说只有两种方向,顺时针和逆时针,记环边为 n,所以环的 ...

  5. [Inside HotSpot] Epsilon GC

    1. Epsilon GC简介 Epsilon GC源于RedHat开发者Aleksey Shipilëv提交的一份JEP 318: Epsilon: A No-Op Garbage Collecto ...

  6. Node.js实现TCP和HTTP并作简单的比较

    TCP和Node 传输控制协议是一个面向连接的协议,换句话说,它是一个传输层的协议,它主要的职务呢,就是确保信息传输的正确性. 我们使用的很多如HTTP协议都是基于TCP的,为什么呢?因为我们不希望传 ...

  7. Ubuntu14.04升级到Ubuntu16.04

    Ubuntu14.04升级到Ubuntu16.04 1.查看目前版本 lsb_release -a 2.执行更新命令 apt-get update && apt-get dist-up ...

  8. Luogu P4901 排队 fib数列+树状数组+倍增

    这题让我升华..还好只重构了一遍 首先我们发现:$n$较小时,整个队伍的形态 跟 $n$ 比较大时的局部是一样的 所以我们预处理出这个队伍的形态,和每一行每个位置的质因子个数的前缀和,$O(nlogn ...

  9. sql新增字段注意事项

    新增字段的类型.长度(精度)是否合适 解决方法: 跟应用明确加字段和改字段的风险,确认新增字段类型正确.长度(精度)合适. 以及跟应用明确老数据是否要订正?如何订正?新增列是否非空?是否有默认值等等. ...

  10. windows 2012 r2 x64 安装IIS注意事项

    详细安装可以参考下面; https://jingyan.baidu.com/article/93f9803f234eade0e46f559f.html 下面只说一些注意事项,如果项目要用到wcf 的话 ...