2019.03.13 ZJOI2019模拟赛 解题报告
得分: \(55+12+10=77\)(\(T1\)误认为有可二分性,\(T2\)不小心把\(n\)开了\(char\),\(T3\)直接\(puts("0")\)水\(10\)分)
\(T1\):Bug 级的存在
感觉只有我这种智障才会觉得这题有可二分性。
显然,若要一段区间能够成为公差为\(d\)的等差序列,则一个基本要求就是这段区间要模\(d\)同余。
因此,我们首先自然就是把每段同余的区间单独抠出来处理。
然后我们把这段区间内的数同整除\(d\)。
但要注意,正数和负数同整除\(d\)可能会出问题,例如\(d=3\)时\(\lfloor\frac{-2}3\rfloor=\lfloor\frac13\rfloor=0\),而且负数在前面的取余操作中也略显麻烦。
因此,我们需要将每个数都减去这个序列中的最小值,这样就可以把每个数都变成非负整数了。
同整除\(d\)之后,对于一个符合条件的区间\([l,r]\),我们应满足其加上至多\(k\)个数后成为一个连续的序列。
即我们需要满足:\(max_{i=l}^ra_i-min_{i=l}^ra_i+1\le(r-l+1)+k\),且不存在相同的\(a_i\)。
之所以要满足这个不等式,因为不等式左半边表示若要使这段区间成为一个连续的序列至少需要的数的总数,右半边\(r-l+1\)表示这段区间内原有的数的个数,\(k\)表示至多可以加数的个数,显然需要数的总数应小于等于原有的数的个数与可以加数的个数。
然后考虑移项,可以化简得到:
则不难发现,在确定\(r\),即确定右端点的情况下,我们显然就是要找到一个最小的\(l\),即左端点满足上述式子。
考虑到在我们从左往右枚举右端点的过程中,可以顺带开两个单调栈来维护\(min\)和\(max\),并考虑用线段树来维护每个点到当前\(r\)的\(max-min+l\)(\(l\)即为每个点的编号)。
最后直接在线段树上查询权值\(\le r+k\)的编号最小的点即可。
但要注意更新在线段树上查找时的下界,即前面提到过的附加条件不存在相同的\(a_i\)。
我们可以开一个\(map\)叫做\(lst\)来记录每个点上次出现的位置,记住每次将询问下界与\(lst[a_i]+1\)取\(min\),然后更新\(lst[a_i]=i\),就可保证不存在相同的\(a_i\)了。
最后,还有一个需要特判的地方,即\(d=0\),这样前面的取模与整除等运算会全部\(RE\)。
因此,我们要特判\(d=0\),而此时也很容易,只要\(O(n)\)扫一遍找出最长的一段元素全相同的区间即可。
代码如下:
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define CL Con LL&
#define I inline
#define W while
#define N 200000
#define min(x,y) ((x)<(y)?(x):(y))
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define LL long long
#define INF 1e9
using namespace std;
int n,k,d,a[N+5],Mod[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int f;char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0,f=1;W(!D) f=c^'-'?1:-1;W(x=tn+(c&15),D);x*=f;}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
#undef D
}F;
class DEqualsToZeroSolver//特判d=0的情况
{
public:
I void Solve()
{
RI i,t=1,ansl=0,ansr=0;
for(i=2;i<=n;++i) a[i]^a[i-1]&&((i-t>ansr-ansl+1)&&(ansl=t,ansr=i-1),t=i);//扫一遍求最长的一段元素全相同的区间
printf("%d %d",ansl,ansr);//输出答案
}
}Zero;
class SegmentTreeSolver//利用线段树维护信息求解答案
{
private:
int ansl,ansr,s[N+5],S1[N+5],S2[N+5];map<int,int> lst;
class SegmentTree//线段树
{
private:
#define STO l,hl,rt<<1
#define ORZ hl+1,r,rt<<1|1
#define PU(x) (O[x]=O[x<<1]+O[x<<1|1])
#define PD(x) (O[x].f&&(O[x<<1]+=O[x].f,O[x<<1|1]+=O[x].f,O[x].f=0))
int n;
struct Interval//存储区间信息
{
LL Mn,f;I Interval(CL m=0,CL p=0):Mn(m),f(p){}
I Interval operator + (Con Interval& t) Con {return Interval(min(Mn,t.Mn));}
I void operator += (CL x) {Mn+=x,f+=x;}
}O[N<<3];
I void upt(CI l,CI r,CI rt,CI ul,CI ur,CI v)//区间修改
{
if(ul<=l&&r<=ur) return O[rt]+=v;PD(rt);RI hl=l+r>>1;
ul<=hl&&(upt(STO,ul,ur,v),0),ur>hl&&(upt(ORZ,ul,ur,v),0),PU(rt);
}
I int ask(CI l,CI r,CI rt,CI v)//查询权值小于等于v的最左端点
{
if(O[rt].Mn>v) return INF;if(PD(rt),!(l^r)) return l;
RI hl=l+r>>1;return O[rt<<1].Mn<=v?ask(STO,v):ask(ORZ,v);
}
I int qry(CI l,CI r,CI rt,CI ql,CI qr,CI v)//查询给定区间内权值小于等于v的最左端点(先找到区间,然后调用ask函数)
{
if(PD(rt),ql<=l&&r<=qr) return ask(l,r,rt,v);RI hl=l+r>>1,res=INF,t;
return ql<=hl&&(t=qry(STO,ql,qr,v),Gmin(res,t)),qr>hl&&(t=qry(ORZ,ql,qr,v),Gmin(res,t)),res;
}
I void Build(CI l,CI r,CI rt)//建树,即清空线段树
{
if(!(l^r)) return (void)(O[rt]=Interval(),0);RI hl=l+r>>1;
Build(STO),Build(ORZ),PU(rt);
}
public:
I void Init(CI x) {n=x,Build(1,n,1);}I void Update(CI l,CI r,CI v) {upt(1,n,1,l,r,v);}
I int Query(CI l,CI r,CI v) {RI t=qry(1,n,1,l,r,v);return min(t,r);}
}T;
public:
I SegmentTreeSolver() {ansl=ansr=1;}
I void Solve(CI l,CI r)
{
RI i,t,cnt=0,T1=0,T2=0,L=1;for(i=l;i<=r;++i) s[++cnt]=a[i]/d;//把区间内所有数整除d的结果存储下来
for(T.Init(cnt),lst.clear(),i=1;i<=cnt;++i)//清空线段树和map,然后枚举右端点i
{
W(T1&&s[S1[T1]]<=s[i]) T.Update(S1[T1-1]+1,S1[T1],s[i]-s[S1[T1]]),--T1;//单调栈维护最小值,同时在线段树上修改-min
W(T2&&s[S2[T2]]>=s[i]) T.Update(S2[T2-1]+1,S2[T2],s[S2[T2]]-s[i]),--T2;//单调栈维护最小值,同时在线段树上修改max
Gmax(L,lst[s[i]]+1),lst[s[i]]=S1[++T1]=S2[++T2]=i,T.Update(i,i,i),//更新询问下界,将i加入栈,并在线段树中i这一位上增加i(因为i之后会被作为左端点,因此要将它加上自身编号)
t=T.Query(L,i,i+k),i-t>ansr-ansl&&(ansl=l+t-1,ansr=l+i-1);//询问最小的满足要求左端点,然后更新答案
}
}
I void PrintAns() {printf("%d %d",ansl,ansr);}//输出答案
}S;
int main()
{
freopen("bug.in","r",stdin),freopen("bug.out","w",stdout);
RI i,t=INF;for(F.read(n,k,d),i=1;i<=n;++i) F.read(a[i]);if(!d) return Zero.Solve(),0;//读入数据,特判d=0的情况
for(i=1;i<=n;++i) Gmin(t,a[i]);for(i=1;i<=n;++i) Mod[i]=(a[i]-=t)%d;Mod[n+1]=-1;//将所有数化为非负整数,然后存下模d的余数
for(t=1,i=2;i<=n+1;++i) Mod[i]^Mod[i-1]&&(S.Solve(t,i-1),t=i);return S.PrintAns(),0;//抠出区间分别处理,然后输出答案
}
\(T2\):猪队友
考试时只会写乱搞。
我们可以设\(f_{i,j}\)表示当\(s_i=j\)时使区间\([i,n]\)内\(s\)与\(t\)相同的最小步数,\(g_{i,j}\)表示先使\(s_i=j\),然后使区间\([i+1,n]\)内\(s\)与\(t\)相同的最小步数。
然后考虑转移\(f_{i,j}\):
-
如果枚举的\(j\)与\(t_i\)相同,则我们无需进行操作即可得到\(s_i=t_i\),直接从上一位转移过来,即:\(f_{i,j}=f_{i+1,s_{i+1}}\)。
-
否则,第一种情况,我们直接替换这一位,即:\(f_{i,j}=f_{i+1,s_{i+1}}+1\)。
-
第二种情况,如果\(s\)的上一位与\(t\)的这一位相同,则我们可以交换这两位,即:\(f_{i,j}=f_{i+1,j}+1\)。
-
然后还有第三种比较复杂的情况,即我们先把上一位变成\(t_i\),且保证区间\([i+2,n]\)内\(s\)与\(t\)相同(不难发现,这就相当于\(g_{i+1,t_i}\)),然后我们把上一位与这一位交换(步数加\(1\)),然后比较\(j\)与\(t_{i+1}\)是否恰好相同,如果不同则需要替换\(j\)为\(t_{i+1}\)(步数再加\(1\)),即:\(f_{i,j}=g_{i+1,b_i}+1+[j==b_{i+1}]\)。
\(g_{i,j}\)的转移与其类似。
最后的答案就是\(f_{1,s_1}\),即在\(s_1=s_1\)时,使整个\(s\)与\(t\)相等的步数。
代码如下:
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define C 26
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,a[N+5],b[N+5],f[N+5][C+5],g[N+5][C+5];char s[N+5],t[N+5];
int main()
{
freopen("teammate.in","r",stdin),freopen("teammate.out","w",stdout);
RI i,j;for(scanf("%s%s",s+1,t+1),n=strlen(s+1),i=1;i<=n;++i) a[i]=s[i]&31,b[i]=t[i]&31;//a与b分别把s与t的字符转化为数字存下来
for(i=1;i<=C;++i) f[n][i]=b[n]^i?1:0,g[n][i]=a[n]^i?1:0;for(i=n-1;i;--i) for(j=1;j<=C;++j)//初始化状态,然后枚举状态进行转移
{
j^b[i]?(f[i][j]=f[i+1][a[i+1]]+1,!(a[i+1]^b[i])&&Gmin(f[i][j],f[i+1][j]+1),Gmin(f[i][j],g[i+1][b[i]]+(j^b[i+1]?1:0)+1)):f[i][j]=f[i+1][a[i+1]];
j^a[i]?(g[i][j]=g[i+1][b[i+1]]+1,!(a[i+1]^j)&&Gmin(g[i][j],f[i+1][a[i]]+1),Gmin(g[i][j],g[i+1][j]+(a[i]^b[i+1]?1:0)+1)):g[i][j]=g[i+1][b[i+1]];
}printf("%d",f[1][a[1]]);//输出答案
}
\(T3\):不可能完成的任务
我们首先需要证明两个结论:
-
结论\(1\):若\(F(a)=t,F(b)=t+C\),则\(a+b=2t-1\)。
证明如下:
由\(F(a)=t,F(b)=t+C\)得:
\[F(a)+C=F(b) \]可以发现这与题目中给出的\(F(x)+C=F(2F(x)-x+1)\)类似,对应可得:
\[a=x,b=2F(x)-x+1,t=F(a)=F(x) \]然后代入式子\(a+b\)中计算可得:
\[a+b=x+(2F(x)-x+1)=2F(x)+1=2t+1 \] -
结论\(2\):\(F(a+2C)=F(a)+2C\)
证明如下:
我们依然像上面一样,设\(F(a')=t',F(b')=t'+C\),依然可得:
\[F(a')+C=F(b') \]两边同时加上一个\(C\),可以得到:
\[F(a')+2C=F(b')+C \]设\(F(c')=F(b')+C\),则\(F(c')=F(a')+2C\)。然后把\(F(c')=F(b')+C\)代入前面的结论\(1\),就可以得到其对应关系为:
\[a=b',b=c',t=F(a)=F(b')=F(a')+C \]因此,我们就可以得到:
\[b'+c'=2(F(a')+C)+1=2F(a')+2C+1=2t'+2C+1=2C+(2t'+1) \]同样由前面的结论\(1\),我们可知\(2t'+1=a'+b'\),代入得:
\[b'+c'=2C+a'+b' \]两边同时减去\(b'\)可得:
\[c'=a'+2C \]代入\(F(c')=F(a')+2C\)可得:
\[F(a'+2C)=F(a')+2C \]
那么证明这两个结论有什么用呢?
由结论\(2\),我们可知,只要知道\(F\)这个函数在\(0\sim2C-1\)范围内的值,就可以由此推得整个函数的值了。
而由结论\(1\),\(a+b=2t+1\),我们可知,\(a\)与\(b\)的奇偶性显然不同。
若假设\(a\)为偶数,则我们可以设\(a=2x,b=2y+1\),且\(0\le a,b\le2C-1\)。
然后设\(F(a)=t\),则我们需要满足下面这个式子才能使\(a\)和\(b\)成功配对:
其中\(n\)为某一未知数。
然后把这个式子移项,可以得到:
把\(a=2x,b=2y+1\)代入,可以得到:
两边同减\(1\),然后就能发现系数都为\(2\),然后同除以\(2\)即可得到:
而\(F(b+2nC)=F(a)+C\),所以:
总结可以得到:
接下来我们考虑题目中给出的\(X_i,Y_i\)。
假设\(X_i=a(mod\ 2C)\),则可设:
因此:
假设:
所以我们就可以得到:
同理,假设\(X_i=b(mod\ 2C)\),则可设:
因此:
假设:
所以我们就可以得到:
于是,最后结果就是\(\sum |W_i+nC|\),显然,根据初一数学可知,\(-nC\)应该尽量接近\(W\)的中位数。
这样,我们就可以求出任意的两个\(x,y\)造成的最小的代价\(p_{x,y}\)。
注意若\(W_i=nC+W'(0\le W'\le C-1)\),则最终使答案最小的应为\(nC\)或者\((n+1)C\)中的一种,需要分别讨论。
接下来,我们要将\(x\)与\(y\)一一配对,就需要做带权二分图匹配。
这里我们考虑写一个状压\(DP\),可以设\(f_i\)表示\(y\)已经被匹配的子集为\(i\)时的最小代价。
若设\(g_i\)表示\(i\)在二进制下\(1\)的个数,由于\(x\)是按顺序匹配的,则显然此时\(x\)被匹配的是前\(g_i\)个,即第\(0\sim g_i-1\)个。
所以就可以推出转移方程为:
具体实现详见代码。
代码如下:
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10000
#define C 16
#define LL long long
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define INF 1e18
using namespace std;
int n,c,s[N+5],X[N+5],Y[N+5],g[1<<C];LL p[C+5][C+5],f[1<<C];
int main()
{
freopen("mission.in","r",stdin),freopen("mission.out","w",stdout);
RI i,j,k,t,px,ax,mx,py,ay,my,S,lim;LL s1,s2;
scanf("%d%d%d%d%d%d%d%d%d%d",&c,&n,&X[1],&px,&ax,&mx,&Y[1],&py,&ay,&my);//读入
for(i=2;i<=n;++i) X[i]=(1LL*X[i-1]*px+ax)%mx,Y[i]=(1LL*Y[i-1]*py+ay)%my;//生成数据
for(i=0;i^c;++i) for(j=0;j^c;++j)//枚举x,y
{
for(t=0,k=1;k<=n;++k) X[k]%(c<<1)==(i<<1)?s[++t]=X[k]-Y[k]-i+j:(X[k]%(c<<1)==(j<<1|1)&&(s[++t]=-X[k]+Y[k]-i+j-c+1));//存储W[i]
if(!t) continue;sort(s+1,s+t+1),S=(s[t+1>>1]%c+c)%c-s[t+1>>1],s1=0,s2=0;//求出中位数
for(k=1;k<=t;++k) s1+=abs(s[k]+S);for(S-=c,k=1;k<=t;++k) s2+=abs(s[k]+S);p[i][j]=min(s1,s2);//分别讨论nC和(n+1)C,记录任意的两个x,y造成的最小的代价p[x][y]
}
for(i=1,lim=1<<c;i^lim;++i) for(g[i]=g[i>>1]+(i&1),f[i]=INF,j=0;j^c;++j) (i>>j)&1&&Gmin(f[i],f[i^(1<<j)]+p[g[i]-1][j]);//状压DP做带权二分图匹配
return printf("%lld",f[lim-1]),0;//输出答案
}