zoukankan      html  css  js  c++  java
  • 【codeforces】【比赛题解】#960 CF Round #474 (Div. 1 + Div. 2, combined)

    终于打了一场CF,不知道为什么我会去打00:05的CF比赛……

    不管怎么样,这次打的很好!拿到了Div. 2选手中的第一名,成功上紫!

    以后还要再接再厉!


    【A】Check the string

    题意:

    一个合法的字符串可以这样生成:

    初始是一个空串,然后小 A 同学往这个串中加入若干(大于零)个字符( exttt{a}),然后小 B 同学往这个串的末尾加入若干(大于零)个字符( exttt{b}),最后小 C 同学往这个串的末尾加入一些字符( exttt{c}),但要满足( exttt{c})的个数等于( exttt{a})的个数或( exttt{b})的个数。

    问一个字符串是否是合法的。

    题解:

    模拟,注意细节。

    #include<bits/stdc++.h>
    
    char str[10000];
    int i,n,A,B,C;
    
    int main(){
    	scanf("%s",str); n=strlen(str);
    	if(str[0]!='a') {puts("NO"); return 0;}
    	for(i=0;i<n;++i) if(str[i]=='a') ; else break;
    	A=i;
    	if(i==n||str[i]!='b') {puts("NO"); return 0;}
    	for(;i<n;++i) if(str[i]=='b') ; else break;
    	if(i==n||str[i]!='c') {puts("NO"); return 0;}
    	B=i-A;
    	for(;i<n;++i) if(str[i]=='c') ; else break;
    	if(i!=n) {puts("NO"); return 0;}
    	C=n-B-A;
    	if(C!=A&&C!=B) {puts("NO"); return 0;}
    	puts("YES");
    	return 0;
    }
    

    【B】Minimize the error

    题意:

    有两个长度都为(n)的数组(A)和(B),定义误差(E=sum_{i=1}^{n}(A_i-B_i)^2)。

    你需要对数组(A)执行恰好(k_1)次操作,对数组(B)执行恰好(k_2)次操作。一次操作即让数组中的一个数加一,或者减一。

    问执行完操作后的最小误差(E)。

    题解:

    数组的每一位都是独立的。对于某一位(A_i,B_i),对这一位执行操作可以让((A_i-B_i)^2=|A_i-B_i|^2)变大,或者变小。

    显然变大是不合算的,所以计算出(C_i=|A_i-B_i|),不论是对(A)还是对(B)的操作都是一样的,可以让(C_i)加一或者减一,但是不能减到负数。

    而(C_i)越大,对它减一的收益就越大,所以每次贪心选取最大的(C_i)减去一。我使用了优先队列来加快速度,尽管暴力可过。

    #include<bits/stdc++.h>
    #define F(i,a,b) for(int i=a;i<=(b);++i)
    #define ll long long
    using namespace std;
    
    int n,A,B;
    int a[100001],b[100001];
    priority_queue<int> pq;
    ll Ans=0;
    
    int main(){
    	scanf("%d%d%d",&n,&A,&B); A+=B;
    	F(i,1,n) scanf("%d",a+i);
    	F(i,1,n) scanf("%d",b+i);
    	F(i,1,n) a[i]=abs(a[i]-b[i]), pq.push(a[i]);
    	F(i,1,A){
    		int x=pq.top(); pq.pop();
    		if(x>0) pq.push(x-1);
    		else pq.push(1);
    	}
    	int x;
    	F(i,1,n) x=pq.top(), Ans+=(ll)x*x, pq.pop();
    	printf("%lld",Ans);
    	return 0;
    }
    

    【C】Subsequence Counting

    题意:

    对于一个数组,将这个数组的所有非空子序列写下,然后删掉满足:最大值与最小值的差大于等于(d)的子序列。

    最后剩下了(X)个子序列。

    请你给出原序列的一种合法排列。

    题解:

    考虑这样构造:

    (overset{k_1}{overbrace{x,cdots,x}},overset{k_2}{overbrace{x+d,cdots,x+d}},overset{k_3}{overbrace{x+2d,cdots,x+2d}},cdots)。

    这样的目的是让任意包含两个不同的数的子序列会被删掉,留下的只有包含相同数的。

    这样最终剩下的子序列有多少个呢?通过一些简单的计算,我们得到答案:

    (sum 2^{k_i}-1)。

    得到了这个,发现两组个数分别为(k_i)和(1)的数,会对答案产生(2^{k_i})的贡献。

    那么把(X)二进制拆分一下即可。

    #include<bits/stdc++.h>
    #define F(i,a,b) for(int i=a;i<=(b);++i)
    #define ll long long
    
    int X,d,cnt;
    ll now=1;
    ll b[100001];
    
    int main(){
    	scanf("%d%d",&X,&d);
    	if(X&1) {b[++cnt]=now; now+=d;}
    	int x=1; X>>=1;
    	while(X){
    		if(X&1){
    			for(int i=1;i<=x;++i) b[++cnt]=now;
    			now+=d;
    			b[++cnt]=now;
    			now+=d;
    		}
    		X>>=1; ++x;
    	}
    	printf("%d
    ",cnt);
    	F(i,1,cnt) printf("%lld ",b[i]);
    	return 0;
    }
    

    【D】Full Binary Tree Queries

    题意:

    给你一个无限层的满二叉树(无限层也不考虑是完全还是满了2333)。

    这个满二叉树的节点上有值,而且一个节点的左儿子的值是这个节点的值乘以2,右儿子的值是这个节点的值乘2加1。

    你有三个操作:

    ①把值为X的节点所在层的所有节点的值往右循环移动K位。

    ②把值为X的节点所在层的所有节点往右循环移动K位,子树也一起移动。

    ③问值为X的节点走到根的路径上的值的序列。

    请执行这些操作,并回答结果。

    题解:

    发现初始一个节点的值可以很轻松算出来,其实就是线段树上的节点的编号,很有规律。

    因为(X)在long long范围,所以深度大于62的都是假的节点。

    考虑暴力维护每一层循环移动的位数。再做做数学加加减减就完事了。

    #include<bits/stdc++.h>
    #define F(i,a,b) for(int i=a;i<=(b);++i)
    #define dF(i,a,b) for(int i=a;i>=(b);--i)
    #define ll long long
    
    int n;
    ll lev[64];
    
    inline int hibit(ll x){
    	int ans=-1;
    	while(x) x>>=1, ++ans;
    	return ans;
    }
    inline void shift(int lv,ll k){lev[lv]+=k&((1ll<<lv)-1);lev[lv]&=((1ll<<lv)-1);}
    
    int main(){
    	scanf("%d",&n);
    	F(i,1,n){
    		int t; ll x,k;
    		scanf("%d%lld",&t,&x);
    		if(t==1){
    			scanf("%lld",&k);
    			shift(hibit(x),k);
    		}
    		if(t==2){
    			scanf("%lld",&k);
    			F(j,hibit(x),61)
    				shift(j,k<<(j-hibit(x)));
    		}
    		if(t==3){
    			int lv=hibit(x);
    			ll y=(x-(1ll<<lv)+lev[lv])&((1ll<<lv)-1);
    			dF(j,lv,0) printf("%lld ",((y-lev[j])&((1ll<<j)-1))+(1ll<<j)), y>>=1; puts("");
    		}
    	}
    	return 0;
    }
    

    【E】Alternating Tree

    题意:

    对于一棵树,假设有一条简单路径(u_1 ightarrow u_2 ightarrow u_3 ightarrow dots u_{m-1} ightarrow u_{m})。

    定义它的交替函数:(A(u_{1},u_{m}) = sumlimits_{i=1}^{m} (-1)^{i+1} cdot V_{u_{i}})。

    一条路径也可以有(0)条边,即(u_1=u_m)。

    请计算所有不同路径的交替函数值的总和。答案对(10^9+7)取模。

    题解:

    考虑对于每一个点计算自己的贡献。

    为了好处理,把树变成以1为根的有根树。

    再考虑一个点的贡献:显然只有经过这个点的路径才会有贡献,即起点在这个点的一个子树(父亲连接的也算作一个子树),终点在其他子树的路径。

    而对于一个路径,显然该点在奇数位贡献为正数,否则是负数。假设当前点为(u)。

    对于起点在某一个大小为(siz)的子树中的(v)点,这个点在(u)的贡献就是((-1)^{dis_{v,u}} imes (n-siz))。

    (n-siz)即为终点可以在的位置的个数,只要不在同一个子树就可以。

    发现只要(v)在同一个子树,最后乘的(n-siz)都是相同的。考虑把前面的也合并起来。

    那么以(u)为根建树,(u)直接相连的每一个子树(T)都定义一个函数(f(T)=sum_{vin T}(-1)^{dep_v})。

    当(v)为(T)的根时,(dep_v=0),之后每一层(dep)就多一。

    这个函数有什么意义呢?发现(f(T))正好就是刚刚的((-1)^{dis_{v,u}})的总和的相反数。

    那就是说子树(T)对(u)的贡献次数是(-f(T) imes (n-siz_T)=-f(T)cdot n+f(T)cdot siz_T)。

    但是还漏了一种起点就是(u)本身的情况,那样会多贡献正好(n)次。

    那就是说,总贡献是((1-sum f(T))cdot n+sum f(T)cdot siz_T)。

    那(siz_T)通过DFS比较好算,考虑一下(f(T))如何求出。

    假如已经以(u)为根了,那么(f(T))可以递归计算:(f(T)=1-sum f(G)),其中(G)是与(T)直接相连的子树。

    这和刚刚的公式是一样的结构,也就是说总贡献为(f(T_u)cdot n+sum f(T)cdot siz_T),(T_u)即以(u)为根的整棵树。

    但是这样还不够,因为要通过DFS计算答案,就必须指定一个点为根,比如(1)号点。

    那么这时还能统计出每个(u)的每个(f(T))吗?

    考虑以(1)为根时的每个(f(T)),那么以(u)为根时,(f(T_u))就等于(f(1))或者(-f(1))。当(u)的深度是偶数(1的深度为0)时,就是(f(1)),否则取相反数。

    而对于(u)的(f(T))呢?对于(u)的儿子的(f(T)),值不变,但是(u)还有一个指向父亲的(T),其实这个(f(T))就等于(f(T_u))【这个指的是以1为根的】减去(f(T_u))【这个指的是以u为根的,即上面算出来的(f(1))或相反数】。

    那么一次DFS求出siz、dep和f值,第二次DFS直接求答案即可。

    #include<bits/stdc++.h>
    #define F(i,a,b) for(int i=a;i<=(b);++i)
    #define eF(i,u) for(int i=h[u];i;i=nxt[i])
    #define ll long long
    
    int Mod=1000000007;
    int n,q;
    int val[200001];
    
    int h[200001],nxt[400001],to[400001],tot;
    inline void ins(int x,int y){nxt[++tot]=h[x];to[tot]=y;h[x]=tot;}
    
    int f[200001],siz[200001],dep[200001];
    
    ll Ans;
    
    void DFS(int u,int fa){
    	f[u]=1; dep[u]=dep[fa]+1; siz[u]=1;
    	eF(i,u) if(to[i]!=fa) DFS(to[i],u), f[u]-=f[to[i]], siz[u]+=siz[to[i]];
    }
    
    void D2(int u,int fa){
    	eF(i,u) if(to[i]!=fa){
    		D2(to[i],u);
    		Ans=((Ans+(ll)f[to[i]]*siz[to[i]]%Mod*val[u]%Mod)%Mod+Mod)%Mod;
    	}
    	if(fa!=0) Ans=((Ans+(ll)(((dep[u]&1)?-f[1]:f[1])+f[u])*(n-siz[u])%Mod*val[u]%Mod)%Mod+Mod)%Mod;
    }
    
    int main(){
    	int x,y;
    	scanf("%d",&n);
    	F(i,1,n) scanf("%d",val+i);
    	F(i,2,n) scanf("%d%d",&x,&y), ins(x,y), ins(y,x);
    	DFS(1,0);
    	F(i,1,n) if(dep[i]&1) Ans+=(ll)f[1]*val[i]; else Ans-=(ll)f[1]*val[i];
    	Ans=(Ans%Mod+Mod)%Mod; Ans=Ans*n%Mod;
    	D2(1,0);
    	printf("%lld
    ",Ans);
    	return 0;
    }
    

    【F】Pathwalks

    题意:

    给定一个有向图,边有边权。

    问最长的路径,满足条件:边权逐个严格增加,并且边的顺序是读入时的顺序。

    只要回答长度即可。

    题解:

    用(dp[i][j])表示走到节点(i),并且最后的边权为(j)时的最长路径长度。

    有转移:(dp[i][j]=max(dp[k][l])+1,(lleq k o i))。

    但是还要边的顺序是读入时的顺序。

    那可以边读入边转移:读入(u o v=w)时,可以转移(dp[v][w]=max(dp[u][l])+1,(lleq w))。

    但是dp[i][j]存储的状态太多了,而且处理最大值也很吃力。

    但是发现能转移的状态很少,而最大值是一个区间。

    于是用动态开点的线段树解决这个问题。不需要离散化。

    【也可以树状数组,因为求得是前缀】

    #include<bits/stdc++.h>
    #define F(i,a,b) for(int i=a;i<=(b);++i)
    using namespace std;
    
    int n,m,Ans;
    int a[100001],b[100001];
    int u[100001],v[100001],w[100001];
    int f[100001];
    
    int dat[8000001],ls[8000001],rs[8000001],cnt;
    
    int Maxi(int rt,int l,int r,int a,int b){
    	if(a>b) return 0;
    	if(r<a||b<l) return 0;
    	if(a<=l&&r<=b) return dat[rt];
    	if(!ls[rt]) ls[rt]=++cnt;
    	if(!rs[rt]) rs[rt]=++cnt;
    	int mid=l+r>>1;
    	return max(Maxi(ls[rt],l,mid,a,b),Maxi(rs[rt],mid+1,r,a,b));
    }
    
    void Ins(int rt,int l,int r,int p,int x){
    	if(l==r) {dat[rt]=x; return;}
    	if(!ls[rt]) ls[rt]=++cnt;
    	if(!rs[rt]) rs[rt]=++cnt;
    	int mid=l+r>>1;
    	if(p<=mid) Ins(ls[rt],l,mid,p,x);
    	else Ins(rs[rt],mid+1,r,p,x);
    	dat[rt]=max(dat[ls[rt]],dat[rs[rt]]);
    }
    
    int main(){
    	scanf("%d%d",&n,&m); cnt=n;
    	F(i,1,m) scanf("%d%d%d",u+i,v+i,w+i), ++w[i];
    	F(i,1,m){
    		f[i]=Maxi(u[i],1,100001,1,w[i]-1)+1;
    		Ins(v[i],1,100001,w[i],f[i]);
    	}
    	F(i,1,m) Ans=max(Ans,f[i]);
    	printf("%d",Ans);
    	return 0;
    }
  • 相关阅读:
    使用NBU进行oracle异机恢复
    mycat偶尔会出现JVM报错double free or corruption并崩溃退出
    exp导出数据时丢表
    service_names配置不正确,导致dg创建失败
    XML概念定义以及如何定义xml文件编写约束条件java解析xml DTD XML Schema JAXP java xml解析 dom4j 解析 xpath dom sax
    HTTP协议简介详解 HTTP协议发展 原理 请求方法 响应状态码 请求头 请求首部 java模拟浏览器客户端服务端
    java集合框架容器 java框架层级 继承图结构 集合框架的抽象类 集合框架主要实现类
    【JAVA集合框架一 】java集合框架官方介绍 Collections Framework Overview 集合框架总览 翻译 javase8 集合官方文档中文版
    java内部类深入详解 内部类的分类 特点 定义方式 使用
    再谈包访问权限 子类为何不能使用父类protected方法
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/8744236.html
Copyright © 2011-2022 走看看