[考试反思]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 }}
View Code

 

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 }}
View Code

 

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 }
View Code

 

posted @ 2020-05-07 17:14  DeepinC  阅读(395)  评论(0编辑  收藏  举报