[考试反思]1015csp-s模拟测试74:压迫
其实同时也是第27,一大片并列的。
真的是越考越烂。
T1是个弱化的贪心原题,15分钟拿下没什么可说的。
T2打的记忆化搜索,hash_mod太小撞哈希了,50->30
T3,想不到正解,90分钟时打完的莫队随机数据可过但是被感人的数据和感人的评测机卡成n2暴力
后来基本一直在卡常
zkt坐在skyh旁边越学越坏了90分钟在厕所跟我说他A了T3
然而LNC说他AK了什么的听听就好了
然而我始终没有A掉T3,不知道对我的心态是否产生了影响
其实我以为我能过的。。。
但是的确没有想到正解,不就是一个裸的二维偏序嘛。。。
考场上想到一个高分暴力之后,正解的思路反而被阻塞了啊。。。
T1:梦境
之前某场考试的《时间机器》的原题。当时是区间与区间匹配。这题弱化了。不讲吧。
1 #include<cstdio> 2 #include<queue> 3 #include<algorithm> 4 using namespace std; 5 struct dream{ 6 int l,r; 7 friend bool operator<(dream a,dream b){ 8 return a.r>b.r; 9 } 10 }d[200005]; 11 bool com(dream a,dream b){ 12 return a.l<b.l; 13 } 14 priority_queue<dream>q; 15 int n,m,p[200005],ans; 16 int main(){//freopen("dream2.in","r",stdin); 17 scanf("%d%d",&n,&m); 18 for(int i=1;i<=n;++i)scanf("%d%d",&d[i].l,&d[i].r); 19 for(int i=1;i<=m;++i)scanf("%d",&p[i]); 20 sort(p+1,p+1+m);sort(d+1,d+1+n,com); 21 for(int p1=1,i=1;i<=m;++i){ 22 while(p1<=n&&d[p1].l<=p[i])q.push(d[p1]),p1++; 23 while(!q.empty()&&q.top().r<p[i])q.pop(); 24 if(!q.empty())q.pop(),ans++; 25 }printf("%d\n",ans); 26 }
T2:玩具
也许算个容斥?算了不瞎说了不然LNC会来ha(n)ck我的
直接算比较难,我们考虑设$f[i][j]$表示$i$个点的树中深度不超过$j$的概率。
直接转移比较困难,因为一棵树可以由一个森林转移而来,那么就设$g[i][j]$表示$i$个点的森林里深度都不超过$j$的概率。
你可以用一个点把森林的每一个根合并起来,$f[i][j]=g[i-1][j-1]$
为了转移,我们需要考虑大的g怎么从小的g转移而来。也就是如何缩小问题规模。
从森林里拿出一棵树,剩下的部分单独考虑就好了。
当然不能乱拿不然会重复,所以我们规定我们只拿编号最小的点所在的树。
设$dp[i][j]$表示$i$个点的图里编号最小的点所在的树的大小为$j$。
可以转移了:$dp[i][j]=dp[i-1][j-1]\times \frac{j-1}{i} + dp[i-1][j] \times \frac{i-j-1}{i} + dp[i-1][j] \times \frac{1}{i}$
后两项合并就是下发题解给的式子。
具体含义是:考虑加入一个点的所有可能,与编号最小的点所在的树上任意一点连边,与其它点连边,或者这个点单独作为一棵新树
最后一种情况容易忘。这也是除$i$而非$(i-1)$的原因了。
考虑g的转移,你随意挑出一个大小为k的树,转移就是$g[i][j]=\sum\limits_{k=1}^{i}g[i-k][j] \times f[k][j]c \times dp[i][k]$
打表发现$dp[i][j]=\frac{1}{i}$,不是巧合,dp[1][1]=1,其余可以归纳证明,所以其实dp数组是无用的。
而f数组完全可以用g表示出来,所以也是多余的。
所以可以用一个g数组干下来。。。
细节稍多,注意处理边界条件,其实都比较好理解,不细说了。
1 #include<cstdio> 2 #define int long long 3 int n,mod,inv[205],ans,g[205][205]; 4 main(){ 5 scanf("%lld%lld",&n,&mod); 6 inv[1]=1;for(int i=2;i<=n;++i)inv[i]=mod-mod/i*inv[mod%i]%mod; 7 for(int i=0;i<=n;++i)g[1][i]=g[0][i]=1; 8 for(int i=2;i<=n;++i)for(int j=0;j<=n;++j)for(int k=1;k<=i;++k) 9 g[i][j]=(g[i][j]+(k==1?1:(j?g[k-1][j-1]:0))*g[i-k][j]%mod*inv[i])%mod; 10 for(int i=1;i<=n;++i)ans=(ans+(mod+g[n-1][i-1]-(i>1?g[n-1][i-2]:0))*i)%mod; 11 printf("%lld\n",ans); 12 }
T3:飘雪圣域
求[l,r]之间的点形成多少联通块。可离线。
联通块数=点数-边数
点数好说,求边数2,也就是边的两个端点都要在[l,r]之间。
经典的二维偏序。
1 #include<cstdio> 2 #include<algorithm> 3 int n,q,ans[200005],t[200005]; 4 void add(int p,int w){for(;p<=n;p+=p&-p)t[p]+=w;} 5 int ask(int p,int a=0){for(;p;p^=p&-p)a+=t[p];return a;} 6 struct mono{int l,r,id;friend bool operator<(mono a,mono b){return a.r>b.r||(a.r==b.r&&a.id>b.id);}}m[400005]; 7 int main(){ 8 scanf("%d%d",&n,&q); 9 for(int i=1;i<n;++i){ 10 scanf("%d%d",&m[i].l,&m[i].r); 11 if(m[i].l>m[i].r)std::swap(m[i].l,m[i].r); 12 add(n+1-m[i].l,1); 13 } 14 for(int i=1;i<=q;++i)scanf("%d%d",&m[i+n-1].l,&m[i+n-1].r),m[i+n-1].id=i; 15 std::sort(m+1,m+n+q); 16 for(int i=1;i<n+q;++i)if(m[i].id)ans[m[i].id]=m[i].r-m[i].l+1-ask(n+1-m[i].l); 17 else add(n+1-m[i].l,-1); 18 for(int i=1;i<=q;++i)printf("%d\n",ans[i]); 19 }
思路积累:
- 题意转化