zoukankan      html  css  js  c++  java
  • 11.24 模拟赛题解

    题解

    题解就随便一点了,能看懂就行了

    题目皆为原题,非原创,下文会标注原题出处

    这次如果大家都不挂分的话应该280或者300以上都没问题,部分分给的很足,出得十分良心。

    算是一场信心赛吧,码量都不算大,思维也不算难。

    T1 打工job

    这是一道十分常规的贪心,搞得我部分分都不知道怎么出了

    (n^2)暴力给了(50) ,但我觉得没有人做不出来T1。

    其实这题就是考验选手有没有轻敌,许多选手刚看到题可能会以为是一个简单模拟或是简单贪心,没有仔细考虑清楚就开始写代码,后果可能会拿不到满分,甚至拿不到高分。

    这题虽说是一道常规贪心,却是贪心中一个经典套路,反悔贪心

    看到题的第一步肯定是想排序截止时间

    正常人当然是想优先完成紧急的工作啦~

    在紧急的工作里自然是优先完成报酬高的工作啦~

    如果你想到这样贪心的话

    枚举1~n项已经排好序的工作

    每做一项工作就day++

    然后符合条件就做

    但是这样就只能拿到一些分

    其实错误的原因很简单(如样例2

    到后期万一都是高报酬的工作(但由于超时做不了了)

    你会不会后悔前期做了哪些低报酬的工作呢?

    所以贪心应该有一个后悔操作,把之前赚钱最少的那个工作去掉,换成现在这个更高报酬的工作。实现可以使用优先队列

    正确思路就是先按截止日期排序,然后如果一个工作有时间去做,就先做了它(听起来有点怪),然后把它的价值压入一个小根堆。当我们找到一个没法做价值比当前堆顶高的工作时,我们就放弃那个最小的工作,用做它的时间去做这个价值更高的工作。

    当然这道题我也发现了许多奇奇怪怪的做法,例如树状数组什么的,有兴趣可以研究一下。

    原题:luogu P2949 [USACO09OPEN]Work Scheduling G

    T2 购物shop

    题意 :给你 (n) 种硬币类型,每种硬币类型 (v_i) 买家有 (c_i) 个,卖家有无限个,买家要买一个 (m) 元的东西(卖家可以找零),买家问你双方之间交流的硬币个数(指买家用的硬币数 + 卖家找零的硬币数)最少为多少个?

    题意中的金额我们不妨抽象为线段长度

    所以题意可以简化成:怎样拼凑成两段线段,且它们的长度之差等于T

    你要怎么去拼凑线段,这就有些背包的感觉。所以我们可以分别从hyfhaha和店主不同的角度来考虑最优解

    因为hyfhaha的硬币数是有限的,所以可以视为多重背包(这里考到了多重背包的二进制优化),求达到一定钱数所需的最少的硬币数量;店长的硬币数是无限的,所以可以视为完全背包,也是求同种方案最少的硬币数量,再在最后枚举每种付钱和找钱的方案硬币数之和,求个min即可得出答案

    但是问题是,我们应该一直找到多少钱的方案才能保证找到合法的最优解呢?我认为这是这道题最关键的部分,以下为证明:

    $ ext { 关于这里的 } m x, ext { 我们令它为 } max {i=1}^{n} v{i}^{2} $

    假设, 在 (m+m x) 的范围内, 没有(买家付的钱(-)卖家找的钱等于 (m) )的情况, 那么卖家所找的硬币的个数必定大于 (v_{max } quadleft( ight.) 因为 (v_{max }^{2}) 显然小于 (left.m x ight)) 。设卖家找的钱的序列为 (pleft(forall p_{i}<p_{i+1} ight)) 我们作一个前缀数组 (s,) 使 (s_{i}=sum_{j=1}^{j leq i} p_{j},) 根据同余的性质,必有两个(或两个以上)的 $s_i $是在 (mod v_{max})意义下是同余的,(因为 (s) 序列长度大于 (v_{max}),而取模后的数最多有 (v_{max })() ,) 那么必然有 (s_{i}-s_{j}=k * v_{max }(i>j)) 即这部分的数可以替换成 (k)(v_{max }) .

    那么, 既然卖家不用还超过 (v_{max }^{2}) 的钱, 那么买家就不用付超过 (m+v_{max }^{2}) 的钱, 证必。

    那么这题也就没了,主要是两个背包,也算是一道比较不常规的背包题吧,但总体不难。

    原题:P2851 [USACO06DEC]The Fewest Coins G

    T3 魔法mogic

    状压+线段树

    这是一道神仙题,想拿满分并不简单,但部分分给的很足,可以考验选手考场时间分配等临场能力

    代码实现起来也比较清真,放T3感觉还可以,思维难度不算大,有点考细节

    简化题面

    给定 (m) 个长度为 (n) 的可能含有 的 01 串,其中 ? 既能代表 0 也能代表 1(q) 次操作,每次给定一个区间,求有多少 01 串满足区间内的所有字符串都可以解释成该 01 串,或者单点修改某个字符串。

    Solution

    子任务 1 :

    乱搞

    子任务 2 :

    考虑 (n) 只有 (10,) 因此可以 (Oleft(2^{n} ight)) 去枚举所有可能的串, 然后对于每个询问 (O(m)) 的逐个判定是否合 法。时间复杂度 (Oleft(q m 2^{n} ight))

    子任务 3 :

    考虑对于一段所有字符串的第 (x) 个字符, 一共有四种可能:确定为 (0 ;) 确定为 (1 ;) 都可以; 都不可以。如

    (0 / 1) 都不可以, 则答案为 (0,) 因为不会有任何一个字符串匹配该区间。如果确定为某个数, 则这一位只

    有一种可能; 否则这一位有两种可能。根据乘法原理, 如果有 (a) 个位置有两种可能, 则本次询问的答案为 (2^{a})

    因此对于每个询问, (O(n m)) 地去遍历区间内所有字符即可。时间复杂度 (O(n m q))

    子任务 4 :

    考虑 (n) 只有 (30,) 可以状压到 int 中。具体的, 维护两个 int,第一个 int 维护对应位是否确定是 0 或 (1,) 第二个 int 维护如果确定是 0 或 1 了那么具体是 0 还是 1 。

    例如, 对于单个字符串, 它所有的为 (?) 的位置, 在第一个 int 中对应位置是 (0,) 所有为 0 或 1 的 位置, 在第个 int 中对应的位置是 (1,) 在第二个 int 中对应的位置是自身的值。

    考虑 (a_{1}, a_{2}) 是询问的左端点到某个字符串之前所维护的两个 int (, b_{1}, b_{2}) 是该字符串的两个 int,现在合并这两个信息。

    如果某一位置即不可以是 (1,) 也不可以是 (0,) 那么该字符串不为 (?) 的位置在 (b_{2}) 中对应的值应该至少有一 个和 (a_{2}) 中对应位置的值且 (a_{1}) 的该位置为 (1,) 位运算可以表现为 (left(a 1 & b_{1} ight) &left(a_{2} oplus b_{2} ight) eq 0,) 则该 询问的答案为 0 。

    否则这两段信息可以合并:将他们已经确定字符的位置合并起来, 然后将确定位置对应的值合并起来即可。于是 (a_{1})(b_{1}) 取或, (a_{2})(b_{2}) 取或即可。

    最终该旬问 (0 / 1) 都可以的位置的个数即为 (a_{1}) 中 1 的个数。

    时间复杂度 (O(m q))

    子任务 5 :

    由于 (n) 只有 (1,) 问题变成了求某个区间内的字符是不是全是 (0,) 全是 (1,) 全是 (?) 或 0 和 1 都有。 可以考虑用线段树非常轻松的维护这样的信息。

    时间复杂度 (O(q log m))

    子任务 6:

    世界上没有什么事情是开一棵线段树不能解决的, 如果有, 那就开 (30) 棵。

    时间复杂度 (O(n q log m))

    子任务 7 :

    考虑结合子任务 4 和子任务 5 的做法, 发现两个区间的状压信息也可以用子任务 4 的方法合并。因此用线段树维护这两个 int 的状压信息即可。

    时间复杂度 (O(q log m))

    原题:P5522 [yLOI2019] 棠梨煎雪

    T4 和平peace

    一道树上计数题,应该都满熟悉这类题的了

    部 分 分 给 得 特 别 多 !

    就算你完全不会正解你也可以拿 (65) 分!

    测试点 1-2

    暴力枚举两条路径的起始点和终点,然后再判断是否相交。时间复杂度 (O(n^4))

    测试点 3-4

    暴力枚举三条路径的起始点和终点,然后再判断是否相交。时间复杂度 (O(n^6))

    这两档部分分虽然很水,但在考场上写出来不算简单,所以给了较多的分数

    方法1:暴力计算贡献

    首先很显然的,用所有的方案减去不合法的方案

    所有的方案就是:树上所有长度不超过(k)的路径条数的(m)次方

    计算不合法的方案要围绕的基准点是什么呢?

    不合法的方案路径一定有交点,考虑枚举交点,但路径交点可能有多个,所以可以枚举深度最浅的交点

    首先求出(f[p])表示经过(p)且路径两端都在(p)子树内的长度不超过(k)的方案数,(g[p])表示经过(p)路径且两端分别在(p)子树内外,长度不超过(k)的方案数

    那么以(p)为深度最浅的交点的不合法方案数就为:(displaystyle sum_{i=0}^{m-1}inom{m}{i}g[p]^i imes f[p]^{m-i})

    也就是选出(i)个国家放到子树外面,注意不能放(m)只在外面,不然最浅的交点就不是(p)了。

    这里时间复杂度为 (O(nm)).

    考虑如何计算(f)(g)

    • 对于(k)一般的情况:(O(n^2))

    对于(p)的一个儿子(q),直接递归(q)的子树,之前的数据都存在前缀桶里,更新答案后,把新的数据扔到桶里就好了

    计算(g[p]),其实就是把子树(p)以外的看成(p)的另外一棵子树,然后类似上面的过程计算就可以了

    • 对于(k=0)的情况:(O(n))

    国家都只能在(p)这个节点上,(f[p]=1,g[p]=0)

    • 对于(k=n-1)的情况:(O(n))

    此时国家可以走完一整棵树,设(displaystyle cal(x)=inom{x}{2}+x),表示在(x)个点中选出(2)个点满足终点不小于起点

    (displaystyle f[p]=cal(size[p])-sum_{qin son(p)}cal(size[q])quad g[p]=(n-size[p]) imes size[p])

    • 对于一条链的情况:(O(n))

    (f[p])就是直接向后扩展,注意和(k+1)(min)(+1)是因为还可以取到自己

    (g[p])取的是前面所有能扩展的向后扩展,对(f[p])求个前缀和,然后再减掉没有到达(p)的就可以了

    这样最后总的时间复杂度是(O(n^2+nm)),代码中直接用快速幂,复杂度多个(log)(反正也不是正解

    方法2:简单的转换

    之前的标算被踩了,本来没有这里的

    方法(1)之所以慢,关键就在于那个求和式,需要(O(m))的时间枚举

    然后这个式子不就是二项式定理的形式吗???

    所以(displaystyle ans=sum_{i=0}^{m}inom{m}{i}g[p]^if[p]^{m-i}-g[p]^m=(f[p]+g[p])^m-g[p]^m)

    这样就可以(O(nlog m))计算了,然后就没了

    但不妨从容斥的角度直接得到最后的答案

    显然这(m)条路径全部相交的点,肯定是形成一条链(边形成的),这条链的长度为(x)

    (f(x))表示这条链的长度为(x)的方案数,(g(x))表示钦定这条链的长度为(x)的方案数,那么就有:

    (displaystyle g(x)=sum_{dgeq x}(d-x+1)f(d)),直接展开得到:

    (g(0)=f(0)+2 imes f(1)+3 imes f(2)+...)

    (g(1)=1 imes f(1)+2 imes f(2)+...)

    显然不合法的方案数就是(displaystyle f(0)+f(1)+...=g(0)-g(1))

    那么只需要求出(g(0))(g(1))就行了,因为(g(1))肯定是两个相连的节点,直接设为每个节点和其父亲就行了

    (g(0))就表示,在树上选出(m)条长度小于等于(k)的链,且至少都经过同一个点

    (g(1))表示,在树上选出(m)条长度小于等于(k)的链,且至少都经过同两个点

    最后要求的东西,其实就是方法(1)求出来的东西了

    不知道有没有人能找到更优的求解 (f)(g) 数组的算法

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #define ll long long
    using namespace std;
    const int N=1e6+10;
    const ll mod=998244353;
    int n,m,k,flag=1,tot,tail;
    int fir[N],dep[N],que[N],size[N],s1[N],s2[N],cnt[N];
    ll f[N],g[N],pre[N];
    struct edge
    {
    	int to;
    	int nxt;
    }e[2*N];
    inline void add(int x,int y)
    {
    	e[++tot].to=y; e[tot].nxt=fir[x]; fir[x]=tot;
    	e[++tot].to=x; e[tot].nxt=fir[y]; fir[y]=tot;
    }
    inline ll power(ll a,int b)
    {
    	ll res=1;
    	for(;b;b>>=1,a=a*a%mod)
    		if(b&1) res=res*a%mod;
    	return res;
    }
    inline ll cal(int x)
    {
    	return 1ll*x*(x-1)/2+x;
    }
    inline void dfs1(int p,int fa)
    {
    	if(p==0) return;
    	dep[p]=dep[fa]+1,que[++tail]=p;
    	for(int i=fir[p];i;i=e[i].nxt)
    		if(e[i].to!=fa) dfs1(e[i].to,p);
    }
    inline void dfs2(int p,int fa)
    {
    	for(int i=0;i<=k;i++) s1[i]=s2[i]=cnt[i]=0;
    	dep[p]=tail=0,dfs1(fa,p);
    	for(int i=1;i<=tail;i++) s1[dep[que[i]]]++;
    	for(int i=1;i<=k;i++) s1[i]+=s1[i-1];
    	f[p]=1,g[p]=s1[k];
    	for(int i=fir[p];i;i=e[i].nxt)
    	{
    		int q=e[i].to;
    		if(q==fa) continue;
    		tail=0,dfs1(q,p);
    		for(int j=1;j<=tail;j++)
    		{
    			if(dep[que[j]]>k) continue;
    			cnt[dep[que[j]]]++;
    			g[p]+=s1[k-dep[que[j]]];
    			f[p]+=s2[k-dep[que[j]]]+1;
    		}
    		for(int j=1;j<=k;j++) s2[j]=s2[j-1]+cnt[j];
    	}
    	for(int i=fir[p];i;i=e[i].nxt)
    		if(e[i].to!=fa) dfs2(e[i].to,p);
    }
    inline void dfs3(int p,int fa)
    {
    	size[p]=1;
    	for(int i=fir[p];i;i=e[i].nxt)
    	{
    		int q=e[i].to;
    		if(q==fa) continue;
    		dfs3(q,p);
    		size[p]+=size[q];
    		f[p]-=cal(size[q]);
    	}
    	f[p]+=cal(size[p]);
    	g[p]=1ll*size[p]*(n-size[p]);
    }
    inline void solve()
    {
    	if(flag) 
    	{
    		for(int i=1;i<=n;i++) 
    		{
    			f[i]=min(n-i+1,k+1);
    			pre[i]=pre[i-1]+f[i]-1;
    			int last=max(1,i-k);
    			ll add=1ll*(i-last-1)*(i-last)/2;
    			g[i]=pre[i-1]-pre[last-1]-add;
    		}
    		return;
    	}
    	if(k==0) 
    	{
    		for(int i=1;i<=n;i++) f[i]=1,g[i]=0;
    		return;
    	}
    	if(k==n-1) dfs3(1,0);
    	else dfs2(1,0);
    }
    int main()
    {
    	int a,b; ll ans1=0,ans2=0;
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<n;i++)
    	{
    		scanf("%d%d",&a,&b);
    		add(a,b);
    		if(a!=b-1) flag=0;
    	}
    	solve();
    	for(int i=1;i<=n;i++)
    	{
    		f[i]%=mod,g[i]%=mod;
    		ans1=(ans1+f[i])%mod;
    		ans2=(ans2+power(f[i]+g[i],m)-power(g[i],m)+mod)%mod;
    	}
    	printf("%lld
    ",((power(ans1,m)-ans2)%mod+mod)%mod);
    	return 0;
    }
    

    题目都不错的,我真良心

  • 相关阅读:
    Git 简要教程
    SDK更新失败问题解决
    常用安卓操作
    MongoDB本地安装与启用(windows 7/10)
    windows 快捷键收集
    windows 常用命令
    Lambda Expression Introduction
    对 load_breast_cancer 进行 SVM 分类
    Support Vector Machine
    使用 ID3 对 Titanic 进行决策树分类
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/14044616.html
Copyright © 2011-2022 走看看