写在前面的一些话
如果我NOIP没退役,这大概会写成一个系列吧,所以这算是系列的开始,要写一些奇怪的东西?
首先解释下什么叫“拔钉子”,其实就是在钉子上做题嘛......至于钉子具体是个什么东西就当面或者QQ问我好了=。=
然后如果写成系列的话前面这些应该都是POI了,那就说说POI好了。我个人还是很喜欢POI的出题风格的,基本上没有什么 Phantasm码农题/仙人板板板子题/很超巨大无比数据结构题(这都是什么啊喂(#`O′) )。思路在解题中比较重要,然后细节有时候也比较需要注意,至于码力这个东西在哪里都可以练......总的来说整体水平可以接受(至少有很多题是我这个马上NOIP退役的蒟蒻能做的),有一点就是经常出现智商题(2333)。当然也有一些很神仙的可能高于省选水平(?)的题,不过看到了可以绕着走就是了,反正只要有NOIP的水平应该都能找到一些可写的题的=。=
然后POI一年好像有17道题,似乎是一年考好几次试总起来的。国内的各个OJ基本上能覆盖掉POI 2016及以前的题,所以这些题在网上或多或少都能找到题解,之前应该也有好多dalao都按年份刷过POI的。不过不知道为什么POI2017只有BZOJ上Claris搬的五道题,剩下的国内OJ好像都没有......(但是为什么有POI2018的啊,钉子上都没有啊=。=)
好了,废话也说够了,开始写我的解题吧=。=
Round I
Flappy Bird
这题和 NOIP 2014 飞扬的小鸟 没有什么关系......
因为这只鸟只要擦过最后一对柱子就可以了,所以我们正着扫一遍,对每个柱子用它和上个柱子的间隔维护小鸟可以飞到的上下边界(上界是用来判无解的),然后这样一路维护到最后得到一个下界$minh$,答案就是$\left\lfloor \frac{x[n]+minh}{2} \right\rfloor$(点一下相当于可以飞两格,可以自己画画)。注意小鸟能飞到的点的横纵坐标之和一定是偶数,这个也需要维护一下=。=
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long n,x,t1,t2,t3,up,down,len,last;
int main ()
{
scanf("%lld%lld",&n,&x);
for(int i=;i<=n;i++)
{
scanf("%lld%lld%lld",&t1,&t2,&t3),len=t1-last;
down=max(down-len,t2+),up=min(up+len,t3-);
if((up-t1)&) up--; if((down-t1)&) down++;
if(up<down) printf("NIE\n"),exit(); last=t1;
}
printf("%lld",(down+last)/);
return ;
}
Divisibility
首先有一个性质(其实我们小学可能都学过,雾):在B进制下一个数是B-1的倍数当且仅当其数位和是B-1的倍数
那么就很好做了,构造方法就是把所有数从大到小接起来,然后如果现在这个数不能整除被B-1整除就扣掉那个余数一位,这样剩余位数是最多的,数就是最大的,然后每个询问lower_bound一下就好了,注意边边角角的细节=。=
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
long long b,q,k,res,tot,bit;
long long cnt[N];
int main ()
{
scanf("%lld%lld",&b,&q);
for(int i=;i<b;i++)
{
scanf("%lld",&cnt[i]);
tot+=cnt[i]*i,bit+=cnt[i];
}
res=tot%(b-);
if(res) cnt[res]--,bit--;
for(int i=;i<b;i++)
cnt[i]+=cnt[i-];
while(q--)
{
scanf("%lld",&k),k++;
long long pos=lower_bound(cnt,cnt+b,k)-cnt;
k<=cnt[b-]?printf("%lld\n",pos):printf("-1\n");
}
return ;
}
Difference Representations
出现了,智商题!
虽然这题非常CF,不太像OI题=。=
发现这个数列每隔两个元素翻一番,所以一会就超过1e9(询问的最大值)了。那么我们暴力预处理前面的数直到它加爆1e9(注意一定是加爆才行),这说明奇数下标的元素和前面那个数的差值已经爆掉1e9了,那么再能凑出来一定是一个偶数下标的元素减去它前面那个元素。
先把预处理的数塞进map里,然后把预处理得到的所有数对的较大下标塞进一个数组$mem$里;对于每个询问先尝试在map里查一下,查不到就在$mem$里面二分出第一个小于它的位置$pos$,这样$pos$前面的都会被凑出来,而后面的就要两个两个每次加$1$来凑,所以设预处理出last个数后爆掉1e9了,那么对于一个数$x$的答案就是$(last+2*(x-pos),last+2*(x-pos)-1)$
#include<map>
#include<cstdio>
#include<cstring>
#include<utility>
#include<algorithm>
using namespace std;
const int N=1e7+,MAXX=1e9;
map<int,pair<int,int> > mp;
map<int,pair<int,int> >::iterator it;
int a[],mem[N],T,p,rd;
void prework()
{
a[]=,a[p=]=,mp[]=make_pair(,);
while(a[p]<=MAXX||p%==)
{
if((++p)&) a[p]=*a[p-];
else
for(int i=;i<=a[p-];i++)
if(mp.find(i)==mp.end())
{a[p]=a[p-]+i; break;}
for(int i=;i<p;i++)
mp[a[p]-a[i]]=make_pair(p,i);
}
for(it=mp.begin();it!=mp.end();it++)
{
pair<int,pair<int,int> > pr=*it;
mem[++mem[]]=pr.first;
}
}
int main ()
{
prework(),scanf("%d",&T);
while(T--)
{
scanf("%d",&rd),it=mp.find(rd);
if(it!=mp.end())
{
pair<int,pair<int,int> > pr=*it;
printf("%d %d\n",pr.second.first,pr.second.second);
}
else
{
int pre=lower_bound(mem+,mem++mem[],rd)-mem-;
printf("%d %d\n",p+*(rd-pre),p+*(rd-pre)-);
}
}
return ;
}
Sabotage
友善的二分答案+树形DP检验
好像挺简单的,设$dp[i]$表示最坏情况下$i$的子树里有几个叛徒,然后每次二分完DP一下就好了(雾
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
const double eps=1e-;
vector<int> son[N];
int dp[N],siz[N];
int n,k,rd,cnt;
double l,r,mid;
void DFS1(int nde)
{
siz[nde]=;
for(int i=;i<(int)son[nde].size();i++)
{
int goal=son[nde][i]; DFS1(goal);
siz[nde]+=siz[goal];
}
}
void DFS2(int nde)
{
if(son[nde].empty())
{dp[nde]=; return;}
int maxx=;
for(int i=;i<(int)son[nde].size();i++)
{
int goal=son[nde][i]; DFS2(goal);
maxx=max(maxx,dp[goal]);
}
dp[nde]=((double)maxx/(double)(siz[nde]-)>mid)?siz[nde]:maxx;
}
bool check()
{
memset(dp,,sizeof dp),DFS2();
return dp[]<=k;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=;i<=n;i++)
{
scanf("%d",&rd);
son[rd].push_back(i);
}
l=,r=,DFS1();
while(r-l>eps)
{
mid=(l+r)/;
check()?r=mid:l=mid;
}
printf("%lf",r);
return ;
}
Tourist
画风突变,做不来,告辞
求竞赛图的哈密顿回路,反正我是弃了,哪位神仙会做一定让我%1%
放个学长的链接吧=。=
Round 2
Sports competition
求一类特殊的二分图匹配的新思路(i207M说lyd讲过,我怎么不记得怕不是当时在摸鱼)
我们将每个左部点连到的右部点互相连起来并记录度数(只有一个右部点就连自己,度数加2),然后就得到了一个基环树森林。那么无解就是因为一棵基环树里的度数超过了点数的二倍,说明这个联通块里的边不够分了。判完无解之后我们就尝试把每棵基环树上的边定向来得到外向基环树,如果可行说明有唯一解;具体来说就是看看每个联通块里都有没有自环,如果有那么这棵基环树一定可以定向成一棵外向基环树,而没有的话就可能有两种定向,这样统计完如果有唯一解再DFS一遍即可得出方案。
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+;
const long long mod=1e9+;
int deg[N],sig[N],xnt[N],vis[N],cir[N],uni[N],outp[N];
int p[N],noww[*N],goal[*N],val[*N];
int n,t1,t2,pw,cnt,tot;
vector<int> col[N];
char rd[];
void link(int f,int t,int v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t,val[cnt]=v;
}
long long qpow(long long x,long long k)
{
if(k==) return x;
long long tmp=qpow(x,k/);
return k%?tmp*tmp%mod*x%mod:tmp*tmp%mod;
}
void DFS(int nde,int fth)
{
if(sig[nde]) uni[tot]=true;
vis[nde]=true,col[tot].push_back(nde);
for(int i=p[nde];i;i=noww[i])
if(goal[i]!=fth)
{
if(vis[goal[i]]) cir[tot]=goal[i];
else DFS(goal[i],nde);
}
}
void mark(int nde,int fth)
{
vis[nde]=true;
for(int i=p[nde];i;i=noww[i])
if(goal[i]!=fth)
{
outp[val[i]]=goal[i];
if(!vis[goal[i]]) mark(goal[i],nde);
}
}
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%s",rd);
if(rd[]=='T')
{
scanf("%d",&t1),sig[t1]=true;
link(t1,t1,i),deg[t1]+=;
}
else
{
scanf("%d%d",&t1,&t2);
link(t1,t2,i),link(t2,t1,i);
deg[t1]++,deg[t2]++;
}
}
for(int i=;i<=n;i++)
if(!vis[i]) tot++,DFS(i,-);
for(int i=;i<=tot;i++)
for(int j=;j<(int)col[i].size();j++)
xnt[i]+=deg[col[i][j]];
for(int i=;i<=tot;i++)
{
if(xnt[i]>*(int)col[i].size())
printf("NIE\n0"),exit();
if(!uni[i]) pw++;
}
if(pw) printf("NIE\n%lld",qpow(,pw)),exit();
memset(vis,,sizeof vis);
for(int i=;i<=tot;i++)
mark(cir[i],-);
printf("TAK\n");
for(int i=;i<=n;i++)
printf("%d\n",outp[i]);
return ;
}
Sum of digits
惊了,CF怕不是出了个POI原题弱化版
这就是CF1070A Find a number的加强版,现在询问数位和为$s$且能被$d$整除的第$k$小数(原题询问最小的数),当然数据范围也减少了不少
然后我咕了,做法应该差不多吧
Strike
又出现了,智商题!
一开始把题想复杂了,问ztb他说可以用线段树维护BFS序(不过这也算学到了,我以前从没想过这种东西233),结果后来又发现好像维护不了。然后i207M说他要写写试试,过了一会他告诉我他A了,根本不用线段树,直接模拟即可=。=
对每个点记录它是否存在和它有几个儿子还存在,然后记录当前点数和边数按题意模拟即可,每次答案即点数-边数,注意根节点特殊考虑一下
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=;
int exi[N],anc[N],son[N];
int p[N],noww[*N],goal[*N];
int n,T,t1,t2,rd,cnt,cnn,cne,root;
void link(int f,int t)
{
noww[++cnt]=p[f];
goal[cnt]=t,p[f]=cnt;
}
void DFS(int nde,int fth)
{
anc[nde]=fth,exi[nde]=true;
for(int i=p[nde];i;i=noww[i])
if(goal[i]!=fth) DFS(goal[i],nde),son[nde]++;
}
int main ()
{
scanf("%d",&n);
for(int i=;i<n;i++)
{
scanf("%d%d",&t1,&t2);
link(t1,t2),link(t2,t1);
}
cnn=n,cne=n-,root=;
DFS(,),scanf("%d",&T);
while(T--)
{
scanf("%d",&rd);
if(rd>)
{
exi[rd]=false,cnn--,cne-=son[rd];
if(rd!=root)
{
son[anc[rd]]--;
if(exi[anc[rd]]) cne--;
}
}
else
{
rd=-rd;
exi[rd]=true,cnn++,cne+=son[rd];
if(rd!=root)
{
son[anc[rd]]++;
if(exi[anc[rd]]) cne++;
}
}
printf("%d\n",cnn-cne);
}
return ;
}
Shipping containers
纪念在钉子上的第一个Unaccepted(Runtime Error),因为没判断越界的问题=。=
根号分类讨论来均摊复杂度,对于距离$d$小于$sqrt(n)$的以$d$为间隔差分一下最后求前缀和,对于距离大于$sprt(n)$的直接暴力做,总复杂度$O(n$ $sqrt(n))$。注意空间可能比较紧,可以把标准调的小于根号$n$一些(钉子是按点测试而且大测试点基本都有3~4s,所以基本不会卡常)
#pragma GCC optimize(2)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=,Sqrt=;
int dif[Sqrt][N],num[N];
int n,m,t1,t2,t3,ed,blo;
int main ()
{
scanf("%d%d",&n,&m),blo=min(,(int)sqrt(n));
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3),ed=t1+(t2-)*t3;
if(t3<=blo)
{
dif[t3][t1]++;
if(ed+t3<=n) dif[t3][ed+t3]--;
}
else
for(int i=t1;i<=ed;i+=t3) num[i]++;
}
for(int i=;i<=blo;i++)
for(int j=;j<=n;j++)
{
if(j-i>=)
dif[i][j]+=dif[i][j-i];
num[j]+=dif[i][j];
}
for(int i=;i<=n;i++)
printf("%d ",num[i]);
return ;
}
Pizza delivery
哇蒟蒻博主发现自己并没有按年份刷的实力和时间,于是......
咕咕咕飞走
如果这只鶸NOIP没退役他大概会回来补的,这之前大概POI的解题还得散着写=。=