【题目记录】——ICPC南京2021

文章目录

  • A Oops, It's Yesterday Twice More 签到
  • C Klee in Solitary Confinement 思维题
  • D Paimon Sorting 思维题
  • H Crystalfly 树上dp
  • J Xingqiu's Joke 记忆化搜索
  • M Windblume Festival 思维

题目集地址 ICPC南京2021
参考博客 第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(南京) CDH
2021 46届icpc 南京

A Oops, It’s Yesterday Twice More 签到

A
题目大意:略
思路:判断离哪个角近,先走到角上再走到(a,b),分类讨论

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;

void solve()
{
	int n,a,b;
	scanf("%d%d%d",&n,&a,&b);
	int x1=n/2;
	int dx=0,dy=0;
	bool mark1=true,mark2=true;
	if(a<=x1)
    {
        mark1=true;
        dx=a-1;
        for(int i=1;i<n;i++)
        {
            printf("U");
        }
    }
    else
    {
        mark1=false;
        dx=n-a;
        for(int i=1;i<n;i++)
        {
            printf("D");
        }
    }
    if(b<=x1)
    {
        mark2=true;
        dy=b-1;
        for(int i=1;i<n;i++)
        {
            printf("L");
        }
    }
    else
    {
        mark2=false;
        dy=n-b;
        for(int i=1;i<n;i++)
        {
            printf("R");
        }
    }
    if(mark1)
        for(int i=1;i<=dx;i++)
        {
            printf("D");
        }
    else
        for(int i=1;i<=dx;i++)
        {
            printf("U");
        }
    if(mark2)
        for(int i=1;i<=dy;i++)
        {
            printf("R");
        }
    else
        for(int i=1;i<=dy;i++)
        {
            printf("L");
        }
}

int main()
{
//	freopen("in.txt","r",stdin);
	int t = 1;
//	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
    return 0;
}

C Klee in Solitary Confinement 思维题

C
题目大意:给出一个数组和数字k,你可以执行1次或者不执行下面的操作
选择一个区间[l,r]将这个区间的所有数加上一个k
最后问整个数组中个数最多的数字的个数最大是多少?
思路:
我一开始一直认为这个要用什么数据结构来做,没有往这个方向考虑。
先预处理出每个数的个数,若k为0,直接输出最大的个数。若不为0,对于每个数,对自己x贡献-1,并且当贡献≤0时,重新置0,意味着不对前面的数+k。然后对x+k贡献+1,并更新最大值,最后输出最大值即可
tcnt数组相当于在某个区间的数加上k后对于x的贡献量,如果贡献量为负数,说明操作此区间必然不可能是最大值,则令tcnt[x]=0,即不操作此区间。

#include <bits/stdc++.h>

using namespace std;
const int offset=2e6;
int tcnt[4000010],num[4000010],a[4000010];
int main()
{
    int n,k,ans=0;
    scanf("%d%d", &n, &k);
    int x;
    for(int i = 1; i<=n;i++)
    {
        scanf("%d",a+i);
        num[a[i]+offset]++;
        ans=max(ans,num[a[i]+offset]);
    }
    if(k!=0)
    {
        for(int i = 1;i <= n;i++)
        {
            x=a[i];
            tcnt[x+offset]--;
            if(tcnt[x+offset]<0)
            {
                tcnt[x+offset]=0;
            }
            tcnt[x+offset+k]++;
            ans=max(ans,tcnt[x+offset+k]+num[x+offset+k]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

D Paimon Sorting 思维题

D
题目大意:给出一段排序代码,给出一个长度为n的随机序列,然后对于此序列的长度为1-n的前缀使用此代码进行排序,分别需要多少次交换。
思路:
因为对于序列的每一个数都要询问一次,且该数后面的数对结果没有影响,故考虑插入法
如果插入的数小于当前最大值,则直到最后一轮之前该数都不会对结果有贡献,最后一轮的贡献则为前面比它大的数的个数(去重后),这里用两个树状数组维护。
如果相等,则始终不会产生贡献。
如果插入的数大于当前最大值,经过手模,观察出为2+当前最大值第二次出现后的数的个数。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e5+9;
int T,n,cnt,flag,maxn;
LL ans;
int a[N],c[N],vis[N];
int lowbit(int x)
{
	return x&(-x);
}
void Add(int x)
{
	while(x<=n)
	{
		c[x]++;
		x+=lowbit(x);
	}
}
int Sum(int x)
{
	int tot=0;
	while(x)
	{
		tot+=c[x];
		x-=lowbit(x);
	}
	return tot;
}
void solve()
{
	cin>>n;
	memset(c,0,sizeof(int)*(n+9));
	memset(vis,0,sizeof(int)*(n+9));
	maxn=cnt=flag=0; ans=0;
	for(int i=1;i<=n;i++)
		cin>>a[i],maxn=max(maxn,a[i]);
	cout<<ans;
	vis[a[1]]=1; Add(a[1]);
	for(int i=2;i<=n;i++)
	{
		if(!vis[a[i]]) vis[a[i]]=1,Add(a[i]);
		if(a[i]==a[1]) flag=1; cnt+=flag-(flag?a[i]>a[1]:0);
		if(a[i]>a[1]) ans+=1+cnt,swap(a[1],a[i]),cnt=flag=0;
		ans+=Sum(a[1])-Sum(a[i]);
		cout<<" "<<ans;
	}
	cout<<"\n";
}
int main()
{
	ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
}

H Crystalfly 树上dp

H
树形dp。f[x]记录子树x得到的最优值;g[x]记录不取x的孩子,也就是走到x惊扰了x的孩子但不去取他们, g [ x ] = f [ y i ] − a [ y i ] g[x]=f[y_i]-a[y_i] g[x]=f[yi]a[yi] y i y_i yi是x的孩子。
从下至上,对每个 x,首先 f [ x ] = m a x ( f [ x ] , g [ x ] + a [ y i ] ) f[x]=max(f[x],g[x]+a[y_i]) f[x]=max(f[x],g[x]+a[yi]),再对他的孩子讨论。
若t[y]=3,可以先走到另一个孩子节点z去取再返回y取,这样就惊扰了z的孩子,因此,z子树的贡献就变成了 g [ z ] g[z] g[z] f [ x ] = m a x ( f [ x ] , g [ x ] + a [ y ] + g [ z ] − ( f [ z ] − a [ z ] ) ) f[x]=max(f[x],g[x]+a[y]+g[z]−(f[z]−a[z])) f[x]=max(f[x],g[x]+a[y]+g[z](f[z]a[z]))。因此只需要记录下 g [ y ] − f [ y ] + a [ y ] g [ y ] − f [ y ] + a [ y ] g[y]-f[y]+a[y]g[y]−f[y]+a[y] g[y]f[y]+a[y]g[y]f[y]+a[y]的最大值和次大值就行了。

#include<bits/stdc++.h>
#define LL long long
#define pb push_back
using namespace std;
const int N=1e5+9;
int T,n;
LL a[N],t[N],f[N],g[N];
vector<int>e[N];
void dfs(int x,int fa)
{
	g[x]=a[x];
	LL maxn1=-1e16-9,maxn2=-1e16-9;
	for(auto y:e[x])
	{
		if(y==fa) continue;
		dfs(y,x);
		g[x]+=f[y]-a[y];
		LL temp=g[y]-f[y]+a[y];
		if(temp>maxn1) maxn2=maxn1,maxn1=temp;
		else if(temp>maxn2) maxn2=temp;
	}
	f[x]=g[x];
	for(auto y:e[x])
	{
		if(y==fa) continue;
		f[x]=max(f[x],g[x]+a[y]);
		if(t[y]==3)
		{
			if(g[y]-f[y]+a[y]==maxn1) f[x]=max(f[x],g[x]+a[y]+maxn2);
			else f[x]=max(f[x],g[x]+a[y]+maxn1);
		}
	}
}
void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i],f[i]=g[i]=0,e[i].clear();
	for(int i=1;i<=n;i++)
		cin>>t[i];
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		e[x].pb(y); e[y].pb(x);
	}
	dfs(1,0);
	cout<<f[1]<<endl;
}
int main()
{
	ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin>>T;
	while(T--) solve();
	return 0;
}

J Xingqiu’s Joke 记忆化搜索

J
题目大意:给你两个数a,b,每次可对两个数同时进行三种操作:加1,减1或者同时除以他们的公共质因数,问使得其中任意一个数到达1的最小操作次数。
思路:我们可以发现,a和b同时加1或者减1,他们的差值不会变,同时如果a和b的公因数必然是他们差值的因数,执行一次除法之后(a,b,b-a)-> ( a g , b g , b − a g ) (\frac{a}{g},\frac{b}{g},\frac{b-a}{g}) (ga,gb,gba),我们需要在执行除法之前将a或者b通过加1减1的操作使得a或b能够被g整除,这样我们就会得到一个递推式,f(a,b-a)=min(f(a/g,(b-a)/b)+a%g+1,f(a/g+1,(b-a)/g)+a-a%g+1))
然后我们进行dfs,我们把中途得到的状态f(x,y)都记录下来,进行搜索,也就是记忆化搜索即可。
代码:

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;

vector<int>v;
unordered_map<ll,int>f; //基于hash,无序,查找快速,用map会T
//这里也可以不用hash而是数对pair
//素数筛
const int N=3e7+1;
bool IsPrime[N];//真值为素数
int Prime[N],cnt;//存储的素数从下标1开始

void ola(int n) { //筛选到n
	memset(IsPrime,1,sizeof(IsPrime));//初始化
	//假设每个数都为素数
	IsPrime[1]=IsPrime[0]=0;
	for(int i=2; i<=n; i++) {
		if(IsPrime[i])//如果这个数没筛掉,那么将其加入素数序列
		{
			Prime[++cnt]=i;
		}
		for(int j=1; j<=cnt&&i*Prime[j]<=n; j++) {
			IsPrime[i*Prime[j]]=0;
			if(!i%Prime[j])
			break;
		}
	}
}

ll H(int a,int c)//hash函数
{
	return a*1e9+c;
}

int dfs(int a,int c)
{
	if(a==1) return 0;
	if(c==1) return a-1;
	if(f[H(a,c)]) return f[H(a,c)];
	int minn=a-1;
	for(auto x:v)
		if(c%x==0)
		{
			int res=a%x;
			minn=min({minn,res+1+dfs(a/x,c/x),x-res+1+dfs(a/x+1,c/x)});
		}
	return f[H(a,c)]=minn;
}

void solve()
{
    ll a,b;
    scanf("%lld%lld",&a,&b);
    if(a>b) swap(a,b);
    int c=b-a;
    v.clear();
    for(int i=1;Prime[i]*Prime[i]<=c;i++)//这里先把a与b的差值c的所有质因数找出来
    {
        if(c%Prime[i]==0)
        {
            v.push_back(Prime[i]);
            while(c%Prime[i]==0)
            {
                c/=Prime[i];
            }
        }
    }
    if(c>1){
        v.push_back(c);
    }
    printf("%d\n",dfs(a,b-a));
}

int main()
{
//	freopen("in.txt","r",stdin);
	ola(N);
	int t = 1;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
    return 0;
}

M Windblume Festival 思维

M
题目大意:n个数字排成圆环状,每次操作可以选择一个数字,让他减去他的下一个数的值,问最后剩下的数字的最大值可能是多少。
思路:有规律如下,若有正数有负数,那就是把所有数字的绝对值加到一起
若全为正数或者全为负数,就将所有值的绝对值加到一起在减去最小绝对值的2倍即可。
n=1时特判一下

#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=1e6+10;
ll a[maxn];

void solve()
{
	int n;
	scanf("%d",&n);
	int k[5]={0};
	ll sum=0,minx=1e18+1;
	if(n==1)
    {
        scanf("%lld",&a[0]);
        printf("%lld\n",a[0]);
        return ;
    }
    for(int i=0;i<n;i++)
    {
        scanf("%lld",&a[i]);
        sum+=abs(a[i]);
        minx=min(minx,abs(a[i]));
        if(a[i]<0)
            k[0]=1;
        if(a[i]>0)
            k[1]=1;
    }
    if(k[0]+k[1]==2)
        printf("%lld\n",sum);
    else
    {
        printf("%lld\n",(sum-2*minx));
    }
}

int main()
{
//	freopen("in.txt","r",stdin);
	int t = 1;
	scanf("%d",&t);
	while(t--)
	{
		solve();
	}
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值