ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

4.30 组队赛 做题记录

2021-05-04 15:04:33  阅读:178  来源: 互联网

标签:return 记录 int ll long 组队 4.30 节点 dp


比赛链接


前言

真是绝了。我还以为考 \(2\) 小时,结果只有 \(100\) 分钟。还好最后慌里慌张调出来踩线交上去了。这次的结果挺满意的,抢了不少首 A。题目还是不错的。


A - Sequence Matching

原题链接:ABC185E

题目大意

给定两个数字串 \(a\) 和 \(b\)。从 \(a\) 和 \(b\) 中移去共 \(x\) 个数字,使得剩下的数字串 \(A\) 和 \(B\) 的长度相等。令 \(y=\sum\limits_{i=1}^{|A|}(A_i\ne B_i)\),输出最小的 \(x+y\)。

分析

显然一个 dp。

设计状态 \(dp_{i,j}\) 表示数字串 \(a\) 前 \(i\) 个数字、\(b\) 前 \(j\) 个数字在满足条件的情况下的 \(x+y\) 的最小值。

考虑转移。

  1. 保留 \(a_i\) 和 \(b_j\)。那么 \(dp_{i,j}=\min(dp_{i-1,j-1}+(a_i\ne b_j))\)

  2. 保留 \(a_i\) 或 \(b_j\)。那么 \(dp_{i,j}=\min(dp_{i,j-1}+1,dp_{i-1,j}+1)\)

  3. 删去 \(a_i\) 和 \(b_j\)。那么 \(dp_{i,j}=\min(dp_{i-1,j-1}+2)\)。但是这种转移没有任何意义,因为 \(+2\) 还不如都保留,保留的话最多也就 \(+1\)。

考虑边界情况。

  1. 当 \(i=0\) 时,需要删去 \(j\) 个数字,此时 \(x=j,y=0\),故 \(dp_{0,j}=j\)。

  2. 当 \(j=0\) 时,需要删去 \(i\) 个数字,此时 \(x=i,y=0\),故 \(dp_{i,0}=i\)。

答案就是 \(dp_{n,m}\)。

代码

#include<bits/stdc++.h>
using namespace std;

const int M=1005;

int n,m,a[M],b[M],dp[M][M];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)
		scanf("%d",&b[i]);
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
		dp[i][0]=i;
	for(int i=1;i<=m;i++)
		dp[0][i]=i;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;
			if(a[i]==b[j])
				dp[i][j]=min(dp[i][j],dp[i-1][j-1]);
			else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);
		}
	printf("%d",dp[n][m]);
	return 0;
}

B - Powerful Discount Tickets

原题链接:ABC141D

题目大意

\(n\) 个物品,买第 \(i\) 个要 \(a_i\) 元。现有 \(m\) 个抵扣券,使用 \(y\) 个抵扣券可以使需要 \(a_i\) 的物品只需要付 \(\left\lfloor\dfrac{a_i}{2^y}\right\rfloor\) 元。求出购买所有物品需要的最少金钱。

分析

贪心。

当 \(\left\lfloor\dfrac{a_i}{2^{x+y}}\right\rfloor\ge 1\) 时,有 \(\left\lfloor\dfrac{a_i}{2^{x+y}}\right\rfloor=\left\lfloor\dfrac{\left\lfloor\dfrac{a_i}{2^x}\right\rfloor}{2^y}\right\rfloor\)。

这并不难理解,将 \(a_i\) 除以 \(2^x\) 再向下取整就相当于将二进制下的 \(a_i\) 右移 \(x\) 位。然后再移 \(y\) 位,就相当于移了 \(x+y\) 位。

那么我们就可以对每个物品的抵扣券 \(1\) 次 \(1\) 次地使用。为了使优惠最大,每次使用都要挑当前序列中最贵的物品,将它的价格减半,然后再放回原序列中,重复 \(m\) 次。这个过程可以用优先队列来进行,实现也非常简单。

代码

#include<bits/stdc++.h>
using namespace std;

int n,m;
priority_queue<int> q;
long long ans=0;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x;scanf("%d",&x);
		q.push(x);
	}
	for(int i=1;i<=m;i++)
	{
		int x=q.top();q.pop();
		x>>=1;
		q.push(x);
	}
	while(!q.empty())
	{
		ans+=q.top();
		q.pop();
	}
	printf("%lld",ans);
	return 0;
}

C - Who Says a Pun?

原题链接:ABC141E

题目大意

给你一个字符串,请找到两个互相不重叠且完全相同的子串,并输出它的最大长度。

分析

二分+哈希。

数据范围这么小,显然可以随便搞。

单调性很显然,长度大了不行,那更大也不行。长度小了可以,那没准能更大。直接二分长度,对于每个 check,暴力判断是否有子串不重叠地出现至少 \(2\) 次,是的话直接 return true

代码

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;

const int M=5e3+5;
const int bas=131;

int n;
char s[M];
ull h[M],pw[M];
ull get(int l,int r)
{
    return h[r]-h[l-1]*pw[r-l+1];
}
map<ull,bool> mp;
bool check(int len)
{
	for(int i=1;i<=n-len+1;i++)
	{
		ull t=get(i,i+len-1);
		for(int j=i+len;j<=n-len+1;j++)
		{
			ull p=get(j,j+len-1);
			if(t==p)return true;
		}
	}
	return false;
}

int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	pw[0]=1;
    for(int i=1;i<=n;i++)
        pw[i]=pw[i-1]*bas;
    for(int i=1;i<=n;i++)
        h[i]=h[i-1]*bas+s[i];
    int l=1,r=n,ans=0;
    while(l<=r)
    {
    	int mid=(l+r)>>1;
    	if(check(mid))
    		l=mid+1,ans=mid;
    	else r=mid-1;
	}
	printf("%d",ans);
	return 0;
}

D - Range Xor Query

原题链接:ABC185F

题目大意

给你一段数列。你的代码需要支持两个操作:

  1. 单点修改:将 \(a_x\) 异或上 \(y\)。

  2. 区间查询:求 \(a_x\oplus a_{x+1}\oplus\dots\oplus a_y\)。

分析

由于异或具备结合律,可以直接统计前缀异或和。其中 \(c_0=0\)。用树状数组维护即可。

代码

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;

const int M=3e5+5;

int n,m,c[M];

void add(int i,int x)
{
	while(i<=n)c[i]^=x,i+=lowbit(i);
}
int que(int i)
{
	int cnt=0;
	while(i>0)cnt^=c[i],i-=lowbit(i);
	return cnt;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x;scanf("%d",&x);
		add(i,x);
	}
	while(m--)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(op==1)add(x,y);
		else printf("%d\n",que(y)^que(x-1));
	}
	return 0;
}

E - Distance Sums

原题链接:ARC103D

题目大意

给出 \(n\) 个互不相同的数 \(D_i\),表示树上的节点 \(i\) 到其他所有点的距离和。

请判断是否存在这样一棵树,其中每条边的长度均为 \(1\)。若存在请输出一种方案,否则输出 -1

分析

构造题。

讲讲我的做题心路:一开始我想的是 \(D_i\) 与距离总和之间的关系,发现过于麻烦且无路可走,弃。然后想到了换根 DP,子节点到所有点距离和和父亲节点到所有点距离和有以下关系:\(D_{fa}=D_u-n+2\times siz_u\)。画个图就能推出。

然后就想从哪里推出父子关系。由于每条边长度为 \(1\),这导致作为根节点的 \(D\) 必然最小,某个叶节点的 \(D\) 必然最大。你往别的任意地方挪都不会变成最值。

对 \(D\) 数组进行从小到大排序,从后向前枚举,根据上述等式推出它父亲节点的 \(D\),通过二分找到编号。一旦找不到,就直接输出 -1

在所有边都连好之后,计算所有点子树的大小之和(不包括根节点),这就是根节点的 \(D\) 的大小。

比如一个节点 \(u\),它会在它到根节点的路径中经过的所有节点的子树中被 \(+1\),同时包括自己。一个点对应一条连往父亲节点的路径,设它到根节点的路径长为 \(x\),那么就会对应 \(x+1\) 个点。所以不算入根节点。

如不相等,也直接输出 -1

然后就把边一一输出就好了。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int M=1e5+5;

int n,siz[M];
struct edge
{
	ll val;int id;
	void init(int i)
	{
		scanf("%lld",&val);
		id=i;
	}
	bool operator<(const edge&x)const
	{
		return val<x.val;
	}
}d[M];
vector<pair<int,int> > re;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		d[i].init(i);
		siz[i]=1;
	}
	sort(d+1,d+n+1);
	for(int i=n;i>1;i--)
	{
		ll nxt=d[i].val-n+2*siz[i];
		edge x={nxt,0};
		int p=lower_bound(d+1,d+i,x)-d;
		if(p>=i||d[p].val!=nxt)
		{
			printf("-1");
			return 0;
		}
		re.push_back(make_pair(d[p].id,d[i].id));
		siz[p]+=siz[i];
	}
	ll sum=0;
	for(int i=2;i<=n;i++)
		sum+=siz[i];
	if(sum!=d[1].val)
	{
		printf("-1");
		return 0;
	}
	for(int i=0;i<re.size();i++)
		printf("%d %d\n",re[i].first,re[i].second);
	return 0;
}

F - Equal Cut

原题链接:ARC100B


题目大意

将长度为 \(n\) 的序列 \(A\) 分为四段不重复的非空子串,最小化四个子串内元素和的最大值与最小值之差。

分析

分四段就是枚举三个断点。直接枚举显然不可能,这个数据范围只允许枚举一个断点。

改变枚举方式,先枚举中间的断点 \(i\)。设四段为 \([1,l]\),\([l+1,i]\),\([i+1,r]\),\([r+1,n]\)。设四段的元素和分别为 \(B,C,D,E\)。

当 \(B<C,D<E\) 时,设 \(B=t-k,C=t+k,D=g-q,E=g+q\)。其中 \(p\ge 0,q\ge 0,2t=p_i,2g=p_n-p_i\)。

那么答案为 \(\max(C,E)-\min(B,D)=\max(2k,2g,k+g)\)。

所以无论如何,都要使 \(k\) 和 \(g\) 的值尽量的小,也就是 \(B\) 和 \(C\),\(D\) 和 \(E\) 的差异要尽量小。通过二分可以找到断点。由于在 \(i\) 往右运动时,\(l\) 和 \(r\) 也在向右运动,所以就当双指针用就行了。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll inf=1e18;
const int M=2e5+5;

int n;
ll a[M],p[M],ans=inf;

ll dis(int a,int b,int c)
{
	ll x=p[b]-p[a-1],y=p[c]-p[b];
	return y-x;
}
ll MAX(ll a,ll b,ll c,ll d)
{
	return max(max(a,b),max(c,d));
}
ll MIN(ll a,ll b,ll c,ll d)
{
	return min(min(a,b),min(c,d));
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		p[i]=p[i-1]+a[i];
	}
	int l=1,r=3;
	for(int i=2;i<n;i++)
	{
		while(l<i&&abs(dis(1,l+1,i))<=abs(dis(1,l,i)))l++;
		while(r<n&&abs(dis(i+1,r+1,n))<=abs(dis(i+1,r,n)))r++;
		ll B=p[l],C=p[i]-p[l],D=p[r]-p[i],E=p[n]-p[r];
		ll t=MAX(B,C,D,E),q=MIN(B,C,D,E);
		ans=min(ans,abs(t-q));
	}
	printf("%lld",ans);
	return 0;
}

G - Or Plus Max

原题链接:ARC100C


题目大意

给你一个长度为 \(2^n\) 的序列 \(a\)。对于每个 \(1\le K\le 2^n-1\),找出二元组 \((i,j)\) 满足 \(i\oplus j\le K\),并最大化 \(a_i+a_j\),输出这个答案。

分析

没写呢还。

参考

官方题解

标签:return,记录,int,ll,long,组队,4.30,节点,dp
来源: https://www.cnblogs.com/cyl06/p/VJ-C436270.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有