前言

关于\(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. 通过增删改查对比Array,Map,Set,Object的使用成本和实现方式

    1.Array 和 Map 对比 { // array and map 增 查 改 删 let map = new Map(); let arr = []; // 增 map.set('a', 1); ...

  2. Educational Codeforces Round 53C(二分,思维|构造)

    #include<bits/stdc++.h>using namespace std;const int N=1e6+6;int x[N],y[N];int sx,sy,n;char s[ ...

  3. Mysql-6-数据类型和运算符

    1.mysql数据类型 (1)数值数据类型:包括整数类型tinyint.smallint.mediumint.int.bigint,浮点小数类型float和double,定点小数类型decimal. ...

  4. 《权限系列shiro+cas》---封装公共验证模块

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 我们现在可以将任意属性传递给每个顶点的顶点着色器使用.但是 ...

  5. Python使用Zero-Copy和Buffer Protocol实现高性能编程

    无论你程序是做什么的,它经常都需要处理大量的数据.这些数据大部分表现形式为strings(字符串).然而,当你对字符串大批量的拷贝,切片和修改操作时是相当低效的.为什么? 让我们假设一个读取二进制数据 ...

  6. 60个DevOps开源工具,你在用哪些?

    你喜欢免费的东西吗?获得开发者社区支持的自动化,开源的工具是大家梦寐以求的.这里列举了 60 多款最棒的开源工具,可以帮助你很好的实行 DevOps. 一.开发工具 版本控制&协作开发 1.版 ...

  7. 1093 Count PAT's(25 分)

    The string APPAPT contains two PAT's as substrings. The first one is formed by the 2nd, the 4th, and ...

  8. Ubuntu批量修改文件后缀

    rename 's/\.JPG/.jpg/' *.JPG 把JPG后缀改为jpg 参考url====http://blog.csdn.net/whuslei/article/details/67249 ...

  9. Hive 基本语法操练(六):Hive 的权限控制

    Hive 的权限控制 Hive从0.10可以通过元数据控制权限.但是Hive的权限控制并不是完全安全的.基本的授权方案的目的是防止用户不小心做了不合适的事情. 为了使用Hive的授权机制,有两个参数必 ...

  10. 【密码学】MD5算法原理

    MD5(单向散列算法)的全称是Message-Digest Algorithm 5(信息-摘要算法),经MD2.MD3和MD4发展而来.MD5算法的使用不需要支付任何版权费用. MD5功能:    输 ...