• 2020.4.5学军信友队趣味网络邀请赛总结


    题目点此
    T1由于是找规律题,不好讲述且讲述意义不大,所以就不说了。

    T2

    给你一棵带点权的树,你要选择一对点((x,y))(a_x>a_y),使得贡献(a_x*dist(x,y))最大。
    求最大贡献。

    第一眼感觉这是一道一定能切的题目。
    第二眼想到可以(LCT)做,但放在第二题肯定不是(LCT)
    第三眼想到树剖,推了推后搞出了一个类似于动态(DP)的方法。
    打了一半感觉常数可能有点大,然后想到点分治。
    点分治一开始打个带set的,后来感觉常数太大,换了另一种打法:
    将子树依次加进去,用线段树维护权值小于等于多少时的最大值,子树加进去的时候计算贡献;正着做一遍,反着做一遍。
    交上去TLE,改成树状数组AC。
    此时比赛开始了约80min。

    领会到正解之后我意识到我sb了……
    题目变一下就是(max(a_x,a_y)*dist(x,y)=max(a_x*dist(x,y),a_y*dist(x,y)))
    所以就是对于每个点找它的最远点的问题……
    找出直径,某个点的最远点肯定是两个直径端点之一。


    T3

    有个数组(a_{1..n}),一开始有个在([1,a_n])(整数)中等概率随机的值(x)(你不知道它具体是多少)。
    可以随时查询(x)的状态,(x)的状态为(i)即为(xin [a_{i-1}+1,a_i])
    然后有若干种操作,每种操作的使用次数和使用顺序是任意的,记为((v_j,w_j))
    表示花(v_j)的代价可以使(x)(w_j)
    问使(x)达到状态(1)的最小步数的期望,结果乘(a_n)

    这个题意可能不太好理解。点明这一点可能就懂了:由于(x)是随机的,所以只能确定(x)在某个区间中而不知道具体的值。所谓“状态”的作用就是进一步区分(x),以影响以后的决策。
    理解了题意之后就会有个很显然的DP,
    (dp_{i,j})表示区间为([i,j])时的答案,转移方程显然。
    接下来脑子一动出现了一个思路,能不能只计算(i)(j)在某个状态的边界时的答案呢?
    于是开打……
    调试的时候发现忽略了区间([i,j])左移之后没有被分割的情况,这时候离结束还没有十分钟……
    于是急了,想改回前面的暴力,可惜时间不够。

    其实我前面的思路都是没有问题的,现在问题是如何处理被分割的情况。
    正解的骚做法就是强制区间在左移后被分割(或者整个区间移到状态(1))。
    显然,一个区间内的数想要变成状态(1),必须要经历这样的过程。
    但是如何保证可以做得到,并且最优呢?
    先对各种操作搞一遍完全背包,那么就相当于有(a_n)种操作,并且保证是最优的。

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <climits>
    #define N 2010
    #define ll long long
    int n,m;
    int a[N];
    int pre[N],suc[N];
    struct Op{
    	int v,w;
    } o[N];
    ll v[N];
    ll f[N],g[N],s[N];
    ll calc(int,int);
    ll getg(int x){return g[x]!=-1?g[x]:g[x]=calc(a[pre[x]]+1,x);}
    ll getf(int x){return f[x]!=-1?f[x]:f[x]=calc(x,a[suc[x]]);}
    ll calc(int L,int R){
    	ll res=0x7f7f7f7f7f7f7f7f;
    	for (int i=1;i<=a[n] && L-i>0;++i)
    		if (v[i]!=0x7f7f7f7f7f7f7f7f && (suc[L-i]!=suc[R-i] || R-i<=a[1])){
    			int l=L-i,r=R-i;
    			ll tmp=v[i]*(r-l+1)+getf(l)+getg(r);
    			l=a[suc[l]]+1,r=a[pre[r]];
    			if (l<=r)
    				tmp+=s[suc[r]]-s[suc[l]-1];
    			res=min(res,tmp);
    		}
    	return res;
    }
    int main(){
    //	freopen("in.txt","r",stdin);
    //	freopen("out.txt","w",stdout);
    	int T;
    	scanf("%d",&T);
    	while (T--){
    		scanf("%d%d",&n,&m);
    		memset(pre,0,sizeof pre);
    		memset(suc,0,sizeof suc);
    		for (int i=1;i<=n;++i)
    			scanf("%d",&a[i]),pre[a[i]+1]=suc[a[i]]=i;
    		for (int i=1;i<=a[n];++i)
    			pre[i]=(pre[i]?pre[i]:pre[i-1]);
    		for (int i=a[n];i>=1;--i)
    			suc[i]=(suc[i]?suc[i]:suc[i+1]);
    		for (int i=1;i<=m;++i)
    			scanf("%d%d",&o[i].v,&o[i].w);
    		memset(v,127,sizeof(ll)*(a[n]+1));
    		v[0]=0;
    		for (int i=1;i<=m;++i)
    			for (int j=o[i].w;j<=a[n];++j)
    				v[j]=min(v[j],v[j-o[i].w]+o[i].v);
    		memset(g,255,sizeof(ll)*(a[n]+1));
    		memset(f,255,sizeof(ll)*(a[n]+1));
    		for (int i=1;i<=a[1];++i)
    			f[i]=g[i]=0;
    		bool bz=1;
    		for (int i=2;i<=n && bz;++i){
    			s[i]=s[i-1]+getg(a[i]);
    			if (g[a[i]]==0x7f7f7f7f7f7f7f7f)
    				bz=0;
    		}
    		printf("%lld
    ",bz==0?-1:s[n]);
    	}
    	return 0;
    }
    
    
    
    
    
    
    
    
    
    
    

    T4

    首先是个博弈题:两个人在玩取石子,石子的总量为(m),并且每个人取的石子数不能超过上一个人取的石子数。(f(m))为先手取最少多少是能保证必胜。
    (sum_{i=1}^nsum_{m|i}f(m))

    这道题比赛的时候根本就没有思考过……

    首先(f(m))是有通项的,可以通过打表或通过下述推理发现:
    如果(m)为奇数,那先手选(1)
    如果(m)为偶数,那在接下来的过程中,谁选了奇数谁就倒霉,所以都会选偶数。于是就将两个石子合二为一,就相当于(f(m/2))
    于是(f(m)=lowbit(m))
    上面那个式子换一下就是(sum_{i=1}^nlowbit(i)lfloorfrac{n}{i} floor)
    考虑将(lowbit(i))相等的分开计算。令(g(n)=sum_{i=1}^nlfloorfrac{n}{i} floor)
    (g(n))(lowbit(i)geq 1)的个数,(g(lfloorfrac{n}{2} floor))(lowbit(i)geq 2)的个数,依次类推。
    答案就是(g(n)+(2-1)g(lfloorfrac{n}{2} floor)+(4-2)g(lfloorfrac{n}{4} floor)+...)
    计算(g(n))的时间是(sqrt n),上面那条式子用等比数列求和的方法分析出来时间是(sqrt n)

    直接整除分块常数很大,卡不过。于是gmh大爷就发明了一种常数极小的方法:
    (xy=n)的图像画出来,可以发现要求的是坐标轴与图图像之间夹的整点数量。
    这个东西是关于(y=x)对称的。
    所以只需要前(sqrt n)一个一个算,扩大两倍之后减去重复部分(即(lfloorsqrt n floor^2)

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #define ll long long
    #define mo 998244353
    ll n;
    inline ll g(ll n){
    	ll sq=sqrt(n);
    	ll res=0;
    	for (ll i=1;i<=sq;++i)
    		res+=n/i;
    	return (2*res%mo-sq*sq%mo+mo)%mo;
    }
    int main(){
    //	freopen("in.txt","r",stdin);
    	scanf("%lld",&n);
    	ll ans=g(n);
    	for (ll k=1;1ll<<k<=n;++k)
    		ans+=(1ll<<k-1)%mo*g(n>>k)%mo;
    	ans%=mo;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    T5

    给出一个成直线排布的山脉,每个位置的海拔高度是随时间单调递减的函数。
    若干个询问([t,l,r]),表示(t)时刻,([l,r])区间的山脉可以储多少水(可以看作不在这个区间内的山脉铲平)。

    答案等于上轮廓减下轮廓,下轮廓就是区间和。
    上轮廓就是找出最高点之后,左边到最高点的前缀最大值和,右边到最高点的前缀最大值和。以下只说左边,右边同理:
    线段树上,对于每个节点所代表的区间动态地维护前缀最大值。
    询问的时候将([l,r])对应的(log)个区间找出来,很好算。
    问题是怎么维护。

    首先,对于一段长度为(L)的区间,前缀最大值的修改次数是(O(L))的。
    因为都是一次函数,某个数的前缀最大值可能在某个时候变成自己,然后在后面的某个时候又变回别人,从此它不可能成为最大值。每个一次函数顶多成为一次最大值,所以修改次数为(O(L))
    试着预处理一下某个点大于左边的数的时间区间。
    处理某个节点所代表的区间的时候,左半部分可以通过左儿子继承过来,
    右半部分可以计算它完全大于左儿子所有数的区间(左儿子建出最大值的分段函数),然后和右儿子的信息取并。
    这样时间复杂度是(O(n lg^2n))的。

    接下来的问题是如何维护前缀最大值(支持区间求和)
    考虑将某个数变成最大值的时候,它会覆盖原先它的前缀最大值所覆盖的区间。
    将某个数不再出最大值时,它原先覆盖的区间被上一个位置的前缀最大值所覆盖。
    修改形如:将某段区间全部赋值为某个数。
    这个修改貌似可以在前面的预处理中一并处理出来。
    这个东西可以套个线段树维护。每个数只需要搞一次,搞一次的时间复杂度是(O(lg n)),一个数会出现在(O(lg n))个区间中,所以总时间是(O(nlg^2 n))
    PS:好像可以用树状数组?

    最终总时间就是(O(n lg^2 n))
    我看std居然有5k,有点吓人。
    表示还没有打,看看以后有没有机会吧。

  • 相关阅读:
    【题解】洛谷 P3942 将军令【20201017 CSP 模拟赛】【贪心】
    ASP.NET上传文件的三种基本方法
    Android 最火的快速开发框架XUtils
    asp.net 上传文件到一般处理程序中
    Android版本:使用findViewById()用字符串/在一个循环
    android 调用系统图库查看指定路径的图片
    Android中实现日期时间选择器(DatePicker和TimePicker)
    Android自定义ListView的Item无法响应OnItemClick的解决办法
    Android开发配置,消除SDK更新时的“https://dl-ssl.google.com refused”异常
    mysql 修改密码
  • 原文地址:https://www.cnblogs.com/jz-597/p/12656489.html
Copyright © 2020-2023  润新知