[考试反思]0507省选模拟90:信任
$STL$这种东西你真是不用还不行用了出锅了你还没话说。。。
$T1$想到正解的一半(而且也写了一半)突然发现不对劲就扔了。
$T2$是个构造。$yy$了半小时虽然不会严谨证但是大概是对的。
写完之后发现过不了样例就懵了,发现少考虑一种边界情况。
结果判这个边界用了好久好久。
并没有给$T3$时间然后我就结束了。
结果$T2$莫名$RE$。把$sort$改成$stable\ sort$就行了。到现在还不明白原理。
反正以后尽量用$stable\ sort$吧。
T1:洛希极限
大意:网格图有$q$矩形,可以从任意点出发任意点结束,每次可以从$(x_1,y_1)$跳到$(x_2,y_2)$当且仅当$x_1<x_2,y_1<y_2$且两个点同时存在于某一个矩形中。
求最多跳几步以及跳这么多步的方案数。多测$T \le 10^5,n,m \le 2 \times 10^3,\sum q \le 5 \times 10^5$
设$dp[i][j]$表示最后一次跳到$(i,j)$的最多步数及方案数。枚举点枚举矩形$O(n^2m^2q)$。
发现最优决策下每一步中,都至少有一个维度的变化量恰好为$1$。
所以我们只要预处理出每个点能从上一行/列的最左/上端那个位置开始转移,然后枚举就好了。$O(qnm+nm^2)$
复杂度变成了两部分。后面这个比较要命(是吗。。?)先来优化这个:
可以发现当你枚举的点右移一个时,转移的左端点不可能左移。也就是说具有单调性,可以单调队列优化。
但是比较麻烦的一点在于,最大值很好统计,但是你还要方案数,也就是说队列里可能堆着很多最大值一样的点。
然后你每次查询的是与最大值相等的点的方案数的和,偶尔还要弹掉队首的一个点。
其实只需要额外维护一个桶来存每种权值的方案数,出入队的时候更新一下桶就行了。
维护了$n+m$个桶和队列,但并不是更新完一个点的权值就加入,而是用到的时候再插入。我思维僵化了。
这样$dp$部分的复杂度下降至线性。总复杂度$O(qn+nm)$。理论很大但是$cbx$实测可以$AC$
(自然也用到了下边这种思想,只不过暴力标记了每一行而已)
考虑优化前半部分。可以拆分成若干这样的问题:对于第$[u,d]$行,每行的$[l,r]$区间对$l$取$min$
我思维僵化,最开始的想法是从上到下($u \rightarrow d$方向)扫描线,$u$时插入$d+1$时删除。然而发现自己并不会删除。
但是由于这道题的特殊性,如果我们从右到左($r \rightarrow l$方向)做扫描线,发现就不需要删除了。
因为可以发现对于$i \le l$的点,你说更新它们的左端点是$l$,显然是没有意义的。反正最后都可以对$i$取$min$
所以就直接做区间取$min$的线段树,每行列扫到的时候遍历一下线段树是$O(n)$的。
所以这一部分的总复杂度就是$O(nm+q\ log\ n)$了。总复杂度也就对了。然而我跑不过$cbx$
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int mod=1e9+7,N=2002; 4 vector<int>ql[N],qr[N],qv[N],Ql[N],Qr[N],Qv[N]; 5 int t,n,m,q; 6 int mo(int x){return x>=mod?x-mod:x;} 7 int add(int&x,int y){x=mo(x+y);} 8 int dec(int&x,int y){x=mo(x-y+mod);} 9 struct D{ 10 int w,p; 11 D operator+(D x){return w==x.w?(D){w,mo(p+x.p)}:(w>x.w?*this:x);} 12 }w[N][N],E,ans; 13 int nt[N<<2],lim[N],lp[N][N],up[N][N]; 14 #define lc p<<1 15 #define rc lc|1 16 #define md (L+R>>1) 17 void build(int p=1,int L=1,int R=1){ 18 nt[p]=N;if(L==R)return; 19 build(lc,L,md);build(rc,md+1,R); 20 } 21 void add(int l,int r,int x,int p,int L,int R){ 22 if(x>nt[p])return; 23 if(l<=L&&R<=r){nt[p]=min(nt[p],x);return;} 24 if(l<=md)add(l,r,x,lc,L,md); if(r>md)add(l,r,x,rc,md+1,R); 25 } 26 void print(int x,int p,int L,int R){ 27 x=min(x,nt[p]);if(L==R){lim[L]=x;return;} 28 print(x,lc,L,md);print(x,rc,md+1,R); 29 } 30 struct que{ 31 int buc[N],pos[N],h,t;D v[N]; 32 void push(int i,D x){ 33 while(t>=h&&v[t].w<x.w)buc[v[t--].w]=0; 34 add(buc[x.w],x.p);v[++t]=x;pos[t]=i; 35 } 36 D top(int l){ 37 while(t>=h&&pos[h]<l)dec(buc[v[h].w],v[h].p),h++; 38 return h>t?(D){0,0}:(D){v[h].w,buc[v[h].w]}; 39 } 40 void clear(){while(t>=h)buc[v[t--].w]=0;h=1;t=0;} 41 }qC[N],qR[N]; 42 int main(){scanf("%d",&t);while(t--){ 43 scanf("%d%d%d",&n,&m,&q); 44 for(int i=1;i<=m;++i)ql[i].clear(),qr[i].clear(),qv[i].clear(),qC[i].clear(); 45 for(int i=1;i<=n;++i)Ql[i].clear(),Qr[i].clear(),Qv[i].clear(),qR[i].clear(); 46 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)w[i][j]=E;ans=E; 47 for(int i=1;i<=q;++i){ 48 int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d); 49 if(a==c||b==d)continue; 50 Ql[c].push_back(b+1);Qr[c].push_back(d);Qv[c].push_back(a); 51 ql[d].push_back(a+1);qr[d].push_back(c);qv[d].push_back(b); 52 } 53 build(1,1,n); 54 for(int i=m;i;--i){ 55 for(int j=0;j<ql[i].size();++j)add(ql[i][j],qr[i][j],qv[i][j],1,1,n); 56 print(m,1,1,n); for(int j=1;j<=n;++j)lp[j][i]=min(i,lim[j]); 57 } 58 build(1,1,m); 59 for(int i=n;i;--i){ 60 for(int j=0;j<Ql[i].size();++j)add(Ql[i][j],Qr[i][j],Qv[i][j],1,1,m); 61 print(n,1,1,m); for(int j=1;j<=m;++j)up[i][j]=min(i,lim[j]); 62 } 63 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){ 64 w[i][j]=(D){0,1}; 65 if(i>1&&j>1)qR[i-1].push(j-1,w[i-1][j-1]); if(j>1&&i>2)qC[j-1].push(i-2,w[i-2][j-1]); 66 w[i][j]=w[i][j]+qR[i-1].top(lp[i][j])+qC[j-1].top(up[i][j]); 67 w[i][j].w++;ans=ans+w[i][j]; 68 }printf("%d %d\n",ans.w,ans.p); 69 }}
T2:特立独行的图
大意:给定图,构造序列$A$及偶数$L$,满足$\forall i\neq j,0 < |A_i - A_j | \le L$。且满足$i,j$右边当且仅当$|A_i - A_j |\le \frac{L}{2}$。$n \le 10^3$
要求$L \le 2 \times 10^9,|A_i| \le 10^9$
首先,把所有$A$同时加上同一个数整个图没有变化。
所以说为了方便,我们可以把值域锁定在$[-\frac{L}{2},\frac{L}{2}]$。
发现,负数与负数之间不可能连边,正数和正数之间也没有连边,正数和负数之间可能有也可能没有。
先不考虑$A_i=0$的情况。那么所有数一定可以分成正数和负数两组,内部都没有边。
就是二分图而已。染色判一下就行。
然后对于两个负数$A_i,A_j$。如果有$A_i < A_j$。那么显然$j$的出边集合含于$i$的出边集合。
所以我们可以对于正负两部点分别按照度数排序,然后判断相邻两个集合是否为包含关系。($bitset$)
如果都是包含关系,那么我们就能确定所有$A$的相对大小关系(连边情况相同的点相对大小随意)
然后只需要从小到大枚举正数,枚举其所有出边依次分配权值即可构造出一组合法解。
然而存在$A_i=0$的情况。这时候图中会出现恰好一个三元环。特盘一下就可以了。
下面这份代码将$stable\ sort$换成$sort$后会$RE$。还请知道原因的大神指点迷津。
大神们告诉我是自定义比较函数必须是小于而不是小于等于,学会了,谢谢大神。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define fail {puts("No");goto F;} 4 int t,n,m,fir[1005],l[1000005],to[1000005],ec,co[1005],r[1005],cnt,sz[1005],tim,ans[1005],A,Z,C,ok; 5 bitset<1001>B[1001]; 6 bool cmp(int a,int b){return sz[a]<=sz[b]||b==A;} 7 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 8 void dfs(int p,int c){ 9 co[p]=c; 10 for(int i=fir[p];i;i=l[i])if(!co[to[i]])dfs(to[i],c^1);else if(co[to[i]]==co[p])ok=0; 11 } 12 int main(){scanf("%d",&t);while(t--){ 13 scanf("%d%d",&n,&m); 14 for(int i=1,a,b;i<=m;++i)scanf("%d%d",&a,&b),link(a,b),link(b,a),B[a][b]=B[b][a]=1,sz[a]++,sz[b]++; 15 for(int i=1;i<=n;++i)for(int j=fir[i];j;j=l[j])if(to[j]>i)for(int k=fir[to[j]];k;k=l[k])if(to[k]>to[j]&&B[i][to[k]])co[i]=co[to[j]]=co[to[k]]=1; 16 17 int tot=0; for(int i=1;i<=n;++i)if(co[i])tot++,C=Z,Z=A,A=i; 18 if(tot>3)fail; 19 if(tot) if(sz[A]==2)swap(A,Z);else if(sz[C]==2)swap(Z,C);else if(sz[Z]!=2)fail; 20 co[A]=co[C]=0; 21 22 ok=1; for(int i=1;i<=n;++i)if(!co[i])dfs(i,2); 23 if(!ok)fail; 24 for(int i=1;i<=n;++i)if(co[i]==2)r[++cnt]=i; 25 26 if(tot){ 27 int mx=0; 28 for(int i=fir[A];i;i=l[i])mx=max(co[to[i]],mx); 29 if(mx<=2)swap(A,C); 30 for(int i=fir[A];i;i=l[i])if(co[to[i]]==2)fail; 31 for(int i=fir[C];i;i=l[i])if(co[to[i]]==3)fail; 32 if(sz[A]!=n-cnt||sz[C]!=1+cnt)fail; 33 ans[C]=-1000000000; 34 } 35 // cerr<<cnt<<endl; 36 // for(int i=1;i<=cnt;++i)cerr<<r[i]<<endl; 37 stable_sort(r+1,r+1+cnt,cmp); 38 for(int i=1;i<cnt;++i)if((B[r[i]]|B[r[i+1]])!=B[r[i+1]])fail; 39 for(int i=1;i<=cnt;++i){ 40 for(int j=fir[r[i]];j;j=l[j])if(!ans[to[j]])ans[to[j]]=(++tim)-1000000000; 41 ans[r[i]]=++tim; 42 } 43 printf("Yes\n2000000000 ");ans[A]=1000000000;ans[Z]=0; 44 for(int i=1;i<=n;++i)printf("%d ",ans[i]);puts(""); 45 F: 46 for(int i=1;i<=n;++i)fir[i]=co[i]=ans[i]=sz[i]=0,B[i].reset(); 47 tim=cnt=ec=A=C=Z=0; 48 }}
T3:玩游戏
大意:求调和级数$k$次前缀和。$n \le 10^{15},k \le 50,\frac{|yourans-stdans|}{stdans} < 10^{-10}$
当然原题意不是这样。但是作为一道证明超过一页半的结论题,有谁在意过程呢?
很奇怪要输出浮点数。因为用到了一个诡异的结论:$H(n)=0.577215664901352 + ln(n) + \frac{1}{2n}$
可以快速求一个调和级数的值。但是在$n$较小时误差较大。所以$\le 10^6$可以直接预处理。
问题在于怎么前缀和。所以构造生成函数$S_k(x)$。
在此区分一下(弱智题解写的乱七八糟):$S_k(x)$表示多项式在$x$处的点值,$S_k[n]$表示多项式的$x^n$项系数。
首先我们知道有一个级数求和:
$ln(1-x) = \sum\limits_{i=1}^{\infty} - \frac{x^i}{i}$。
右边的那个东西的相反数就是$A_i=\frac{1}{i}$的生成函数。做一遍前缀和就是调和级数。
然后众所周知如果我们要给一个多项式做前缀和,应该卷积上这个式子:
$\sum\limits_{i=0}^{\infty} x^i$
等差数列求和也是级数求和得到$\frac{1}{1-x}$
所以说调和级数的生成函数就是$\frac{-ln(1-x)}{1-x}$。这也就是$S_0(x)$
然后按照题意再做若干次前缀和的话同理就有$S_k(x)=\frac{-ln(1-x)}{(1-x)^{k+1}}$
考虑对$S_k(x)$求导:
$S'_k(x)=(\frac{-ln(1-x)}{(1-x)^{k+1}})'$
$=\frac{\frac{(1-x)^{k+1}}{1-x} - ln(1-x)(k+1)(1-x)^k }{((1-x)^{k+1})^2}$(除法求导公式)
$=\frac{1}{(1-x)^{k+2}} - (k+1) \frac{ln(1-x)}{(1-x)^{k+2}}$(两边拆开并约分)
$=\frac{1}{(1-x)^{k+2}} + (k+1) S_{k+1}(x)$(回代$S_{k}(x)$的生成函数式)
这样我们貌似就发现了一个可以递归的形式。
首先我们明显知道$S'_k[n-1] = n S_k[n]$
同时,还根据上面推的那一大堆,我们也能表示出$S'_k[n-1]$
前面的分式用级数求和捯回去(啊对是泰勒展开)是$(\sum\limits_{i=0}^{\infty} x^i )^{k+2}$
要求这个东西的第$n-1$次系数,那么含义考虑,是每次可以走任意步,要求$k+2$次之内走了$n-1$步。
经典插板得到$\binom{n+k}{k+1}$
式子的右半部分的系数就是$(k+1)S_{k+1}[n-1]$
所以我们现在通过两种方式表示出了$S'_{k}[n-1]$。两种画等号得到:
$n S_k[n]= \binom{n+k}{k+1} + (k+1)S_{k+1}[n-1]$
为了方便我们让$k++,n--$,然后稍作移项:
$S_k[n] = \frac{n+1}{k} S_{k-1}[n+1] - \frac{\binom{n+k}{k}}{k}$
$S_0[n]$我们可以用调和级数的公式轻松求得。组合数暴力求,就可以递归得到答案了。
时间复杂度$O(k^2)$。
题解给出了另一种没有证明的方法,在此记录一下:$ans=\frac{n^k}{k!} (S_0(n)-S_0(k))$
1 #include<iostream> 2 #include<cmath> 3 using namespace std; 4 long double H[1000005]; 5 long double h(long long x){return x>1000000?.577215664901352L+log(x)+.5L/x:H[x];} 6 long double S(int k,long long n){ 7 if(!k)return h(n); 8 long double C=1.L/k; 9 for(int i=1;i<=k;++i)C=C*(n+i)/i; 10 return S(k-1,n+1)/k*(n+1)-C; 11 } 12 int main(){ 13 cout.unsetf(ios::fixed),cout.setf(ios::scientific),cout.precision(9); 14 for(int i=1;i<=1000000;++i)H[i]=H[i-1]+1.L/i; 15 long long n,k;cin>>k>>n; 16 cout<<S(k,n); 17 }