2019年浙江ACM省赛题解报告(B、E、F、G、H、I、J、K)

本文解析了五个编程挑战问题,涉及数学运算、分类讨论、字符串处理、思维算法和社交策略。从ElementSwapping中的斐波那契性质到SequenceinthePocket的非递减序列优化,再到Abbreviation和Lucky7inthePocket的简单签到题目,以及模拟与策略的SingingEverywhere和WelcomeParty,最后是字符串处理的Manacher算法应用。
摘要由CSDN通过智能技术生成

B.Element Swapping (数学+分类讨论)

链接: ZOJ-4101
题意:
数 组 a 通 过 交 换 一 对 数 字 , 得 到 了 b 数 组 。 数组a通过交换一对数字,得到了b数组。 a,b
给 出 如 下 的 两 个 值 x = ∑ k = 1 n k a k 和 y = ∑ k = 1 n k a k 2 和 b 数 组 . 给出如下的两个值x = \sum_{k=1}^{n}ka_k和y = \sum_{k=1}^{n}ka^2_k和b数组. x=k=1nkaky=k=1nkak2b.
问 可 能 有 多 少 种 交 换 方 式 使 得 满 足 条 件 。 问可能有多少种交换方式使得满足条件。 使
x 和 y 的 值 有 可 能 是 和 数 组 不 匹 配 的 , 这 样 的 情 况 时 输 出 0 。 x和y的值有可能是和数组不匹配的,这样的情况时输出0。 xy0
题解:
已 知 x = ∑ k = 1 n k a k 和 y = ∑ k = 1 n k a k 2 , 已知x = \sum_{k=1}^{n}ka_k和y = \sum_{k=1}^{n}ka^2_k, x=k=1nkaky=k=1nkak2
则 令 x ′ = ∑ k = 1 n k b k 和 y ′ = ∑ k = 1 n k b k 2 , 则令x' = \sum_{k=1}^{n}kb_k和y' = \sum_{k=1}^{n}kb^2_k, x=k=1nkbky=k=1nkbk2
假 设 a i , a j 交 换 成 为 了 b i , b j , 即 a i = b j , a j = b i 。 假设a_i,a_j交换成为了b_i,b_j,即a_i=b_j,a_j=b_i。 aiajbibjai=bjaj=bi

有 X = x ′ − x = i b i + j b j − i a i − j a j = ( b i − b j ) ∗ ( i − j ) 有X=x'-x=ib_i+jb_j-ia_i-ja_j=(b_i-b_j)*(i-j) X=xx=ibi+jbjiaijaj=(bibj)(ij)
有 Y = y ′ − y = i b i 2 + j b j 2 − i a i 2 − j a j 2 = ( b i 2 − b j 2 ) ∗ ( i − j ) 有Y=y'-y=ib_i^2+jb_j^2-ia_i^2-ja_j^2=(b_i^2-b_j^2)*(i-j) Y=yy=ibi2+jbj2iai2jaj2=(bi2bj2)(ij)

易 得 , Y X = ( b i 2 − b j 2 ) ∗ ( i − j ) ( b i − b j ) ∗ ( i − j ) = b i + b j = C ( 常 数 ) 易得,\frac{Y}{X}=\frac{(b_i^2-b_j^2)*(i-j)}{(b_i-b_j)*(i-j)}=b_i+b_j=C(常数) XY=(bibj)(ij)(bi2bj2)(ij)=bi+bj=C
我 们 知 道 了 两 个 交 换 的 数 之 和 , 则 可 通 过 b i 求 得 b j = Y X − b i , 枚 举 b i , 可 求 得 b j , 然 后 可 通 过 X = ( b i − b j ) ∗ ( i − j ) 得 到 j 的 下 标 i − X b i − b j , 只 需 验 证 求 得 的 下 标 j 是 否 满 足 b j 即 可 。 我们知道了两个交换的数之和,则可通过b_i求得b_j=\frac{Y}{X}-b_i,枚举b_i,可求得b_j,然后可通过X=(b_i-b_j)*(i-j)得到j的下标i-\frac{X}{b_i-b_j},只需验证求得的下标j是否满足b_j即可。 bibj=XYbibibj,X=(bibj)(ij)jibibjX,jbj

需 要 注 意 的 是 : 需要注意的是: :
当 X ≠ 0 , Y % X ≠ 0 时 , x , y 不 匹 配 , a n s = 0 ; 当X \not = 0,Y\%X \not =0时,x,y不匹配,ans=0; X=0Y%X=0,x,yans=0;
当 X = 0 , Y ≠ 0 时 , x , y 不 匹 配 , a n s = 0 ; 当X = 0,Y \not = 0时,x,y不匹配,ans=0; X=0Y=0,x,yans=0;
当 X = Y = 0 时 , 因 为 交 换 前 后 无 变 化 , 则 a i = a j , 枚 举 每 个 数 出 现 的 次 数 n u m i , a n s = ∑ i = 1 n n u m i ∗ ( n u m i − 1 ) 2 当X=Y=0时,因为交换前后无变化,则a_i=a_j,枚举每个数出现的次数num_i,ans=\sum_{i=1}^{n}\frac{num_i*(num_i-1)}{2} X=Y=0ai=aj,numians=i=1n2numi(numi1)
代码:

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

#define ll long long

ll T,n,x,y,c;
ll suma,sumb,sum;
ll a[200500],b[200500];
unordered_map<ll,ll>dyy;
int main(){
	cin>>T;
	while(T--)
	{
		cin>>n>>x>>y;
		suma=sumb=sum=0;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];suma+=a[i]*i;
			b[i]=a[i]*a[i];sumb+=b[i]*i;
		}
		x=suma-x;//X
		y=sumb-y;//Y
		if(x!=0)
		{
			if(y%x)
			{
				cout<<0<<endl;
				continue;
			}
			else c=y/x;//c=ai+aj
			for(int i=1;i<n;i++)
			{
				ll aj=c-a[i];
				if(a[i]-aj==0)continue;//检查除数a_i-a_j是否为0,因为X>0,为0则跳过
				if(x%(a[i]-aj))continue;//防止x和y不匹配
				int j=i-x/(a[i]-aj);//计算j的下标
				if(j<=i||j>n)continue;//防止重复,对于每个i只查找之后的j
				if(a[j]==aj)sum++;
			}
		}
		else
		{
			if(y!=0)
			{
				cout<<0<<endl;
				continue;
			}
			dyy.clear();
			for(int i=1;i<=n;i++)
			{
				dyy[a[i]]++;
			}
			for(auto t:dyy)
			sum+=t.second*(t.second-1)/2;
		}
		cout<<sum<<endl;
	}
	return 0;
}

E.Sequence in the Pocket (思维)

链接:ZOJ-4104
题意:
DreamGrid刚刚找到一个整数序列a1, a2, … , an 在他的右口袋里。当DreamGrid感到无聊时,他决定使用该序列。他可以执行任意次(包括零次)以下操作:选择一个元素并将其移到序列的开头。

使序列成为非递减序列所需的最少操作数是多少?

题解:
从最大值往前遍历。
遇到次大值,则次大值有序,继续往前寻找次次大值。
若遍历不到第 k k k大值,说明第 k k k大值位于 k − 1 k-1 k1值的后面,需要调至第一位,第k位之后的值也均需重新挪动至第一位才能保持序列非递减。
所以, a n s = ans= ans=无法被遍历到的第 k k k大值及之后数字的个数。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll n,t,k;
ll a[100500],b[100500];
int main(){cout.tie(0);
	cin>>t;
	while(t--)
	{
		cin>>n;k=n;
		for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
		sort(b+1,b+1+n);
		for(int i=n;i;i--)
		{
			if(a[i]==b[k])k--;
		}
		cout<<k<<endl;
	}
	return 0;
}

F.Abbreviation(签到)

链接:ZOJ-4105
题意:
去除从第二个字母开始的元音。

题解:
签到题
代码:

#include<bits/stdc++.h>
using namespace std;
int n;
string s,str;
bool check(char x)
{
	if(x=='str'||x=='e'||x=='i'||x=='y'||x=='o'||x=='u')return 0;
	else return 1;
}
int main(){
	cin>>n;
	while(n--)
	{
		cin>>s;
		str=s[0];
		//cout<<s<<endl<<a<<endl;
		for(int i=1;i<s.size();i++)
		{
			if(check(s[i]))str=str+s[i];
		}
		cout<<str<<endl;
	}
	return 0;
}

G.Lucky 7 in the Pocket(签到)

链接:ZOJ-4106
题意:
输入一个数字,找比他大的能被7整除不能被4整除的最小数字。

题解:
签到题

代码:

#include<bits/stdc++.h>
using namespace std;
int n,x,t;
bool check(int x)//判断是否是符合要求
{
	if(x%7==0&&x%4!=0)return 1;
	else return 0;
}
int main(){
	cin>>n;
	while(n--)
	{
		cin>>x;
		if(check(x))cout<<x<<endl;
		else
		{
			x=x+7-x%7;//找到最小的比x大的7的倍数
			while(!check(x))x+=7;
			cout<<x<<endl;
		}
	}
	return 0;
}

H.Singing Everywhere (模拟)

链接:ZOJ-4107
题意:
将山峰定义为一个序列中大于左右两边的数,问删掉序列中的一个数后,序列中山峰数量最少的值。

题解:
每个点都删一遍,判断对于总体情况是否减少:
①当前点是山峰,删掉当前点时要注意两端是否会形成新的山峰? a n s ans ans: a n s − 1 ans-1 ans1
②当前点两边是山峰,如果两边高度相同则 a n s − 2 ans-2 ans2;
③端点旁有山峰 a n s − 1 ans-1 ans1
取最优情况即可
代码:

#include<bits/stdc++.h>
using namespace std;
int T,n;
int a[100500],b[100500];//b[i]记录点i是否为山峰
int ans,k;
int main(){
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++){cin>>a[i];b[i]=0;}
		ans=0;
		for(int i=2;i<n;i++)
			if(a[i-1]<a[i]&&a[i]>a[i+1]){ans++;b[i]=1;}
		k=0;
		if(b[2]||b[n-1])k=1;
		for(int i=2;i<n;i++)
		{
			if(b[i])
			{
				if(i==2||i==n-1){k=max(k,1);continue;}
				else if((a[i-1]>a[i-2]&&a[i-1]>a[i+1])||(a[i+1]>a[i+2]&&a[i+1]>a[i-1]))continue;
				k=max(k,1);
			}
			if(b[i-1]&&b[i+1])
			{
				if(a[i-1]==a[i+1])k=max(k,2);
				else k=max(k,1);
			}
		}
		cout<<ans-k<<endl;
	}
}

I.Fibonacci in the Pocket (思维)

链接:ZOJ-4108
题意:
斐波那契数列,给定a,b,求 ∑ i = a b f ( i ) \displaystyle\sum_{i=a}^{b}f(i) i=abf(i)的奇偶性。

题解:
斐波那契数列前六项奇偶性为1、1、0、1、1、0。
易得,奇偶性3位一个周期,即 f ( x ) f(x) f(x) f ( x % 3 ) f(x\%3) f(x%3)奇偶性相同。
欲求 ∑ i = a b f ( i ) \displaystyle\sum_{i=a}^{b}f(i) i=abf(i)奇偶性,即求 f ( a ) f(a) f(a) f ( b ) f(b) f(b)间奇数的个数。同时每个循环节提供两个奇数,对结果无影响,因此可以去除 f ( a ) f(a) f(a) f ( b ) f(b) f(b)的循环节。题目即可转化成求 f ( a % 3 ) f(a\%3) f(a%3) f ( b % 3 ) f(b\%3) f(b%3)间奇数的个数。

代码:

#include<bits/stdc++.h>
using namespace std;
int n,x,i,x1,x2;
int a[10];
string s1,s2;
int main(){
	cin>>n;
	a[0]=0;a[1]=1;a[2]=1;
	a[3]=0;a[4]=1;a[5]=1;
	while(n--)
	{
		cin>>s1>>s2;
		x=x1=x2=0;
		for(i=0;i<s1.size();i++)x1+=s1[i];
		x1=x1%3;
		for(i=0;i<s2.size();i++)x2+=s2[i];
		x2=x2%3;
		if(x2<x1)x2+=3;
		for(i=x1;i<=x2;i++)x+=a[i];
		x=x%2;
		cout<<x<<endl;
	}
	return 0;
}

J.Welcome Party (并查集+优先队列)

链接:ZOJ-4109
题意:
给出 n n n个人,用 1   n 1 ~ n 1 n标号。
m m m个关系,每行 a , b a,b a,b表示 a a a b b b是朋友,朋友的关系不可传递。
如果一个人进场的时候场内没有自己认识的人则他会不开心,求最少的不开心人数,和一个合适的入场顺序使不开心的人数最少并且序列字典序最小。

题解:
先根据朋友关系跑一遍并查集,跑出的块数即为不开心的人数(每个朋友集体必有一个人进场的时候没遇到朋友)。
入场顺序:将每块中 i d id id最小者放入优先队列,然后在让优先队列中最小 i d id id者先进场,并将这位人的朋友们放入优先队列。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
vector<int>G[1000600];
int T,n,m,x,y,ans,cnt;
int par[1000600],vis[1000600],res[1000600];
void init(int n)
{
	for(int i=1;i<=n;i++)
	{
		par[i]=i;
		G[i].clear();
		vis[i]=1;
	}
}
int find(int x)
{
	return par[x]==x?x:par[x]=find(par[x]);
}
void unite(int x,int y)
{
	x=find(x);y=find(y);
	if(x==y)return;
	if(y<x)par[x]=par[y];
	else par[y]=par[x];
}
void solve()
{
	ans=cnt=0;
	priority_queue<int,vector<int>,greater<int> >q;
	for(int i=1;i<=n;i++)
	{
		if(par[i]==i)
		{
			q.push(i);
			vis[i]=0;
			ans++;
		}
	}
    while(!q.empty())
    {
        int x=q.top();
        res[++cnt]=x;
        q.pop();
        for(int i=0; i<G[x].size(); i++)
        {
            int y=G[x][i];
            if(vis[y])
            {
                q.push(y);
                vis[y]=0;
            }
        }
    }
	cout<<ans<<endl;
	for(int i=1;i<cnt;i++)cout<<res[i]<<' ';cout<<res[cnt]<<endl;
}
int main(){
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		init(n);
		for(int i=1;i<=m;i++)
		{
			cin>>x>>y;
			G[x].push_back(y);
			G[y].push_back(x);
			unite(x,y);
		}
		solve();
	}
	return 0;
}

K.Strings in the Pocket (Manacher)

链接:ZOJ-4110

题意:

给 你 两 个 串 s 和 t , 翻 转 一 次 s 串 的 一 个 区 间 [ l , r ] , 问 有 多 少 对 l , r 使 得 翻 转 后 的 s 串 等 于 t 串 。 给你两个串s和t,翻转一次s串的一个区间[l,r],问有多少对l,r使得翻转后的s串等于t串。 st,s[l,r],l,r使st
题解:
s = t 时 , a n s = s 串 中 回 文 个 数 , 可 由 M a n a c h e r 算 法 求 得 。 s=t时,ans=s串中回文个数,可由Manacher算法求得。 s=tans=sManacher
s ≠ t 时 , 找 出 最 左 最 右 的 不 等 坐 标 l 、 r , 先 判 断 区 间 [ l , r ] 翻 转 后 s 、 t 串 是 否 相 等 , 不 相 等 则 无 解 ; s \not = t时,找出最左最右的不等坐标l、r,先判断区间[l,r]翻转后s、t串是否相等,不相等则无解; s=tlr[lr]st
相 等 的 话 就 将 l 、 r 向 外 扩 展 , 看 是 否 一 致 。 a n s = 扩 展 次 数 + 1 相等的话就将l、r向外扩展,看是否一致。ans=扩展次数+1 lrans=+1
代码:

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

#define ll long long
void manacher(char *str,ll *hw);
string s,t;
char str[5006000];
ll hw[5006000];
ll n,T,l,r,ans,f;
int main(){
	cin>>T;
	while(T--)
	{
		cin>>s>>t;
		l=r=-1;
		ans=0;
		n=s.size();
		for(int i=0;i<n;i++)
		{
			if(s[i]!=t[i])
			{
				l=i;
				break;
			}
			str[i]=s[i];
		}
		if(l==-1)
		{
			manacher(str,hw);
			for(int i=0;i<n;i++)ans+=hw[i]/2;
		}
		else
		{
			for(int i=n-1;i>=0;i--)
			{
				if(s[i]!=t[i])
				{
					r=i;
					break;
				}
			}
			f=1;
			for(int i=l;i<=r;i++)
			{
				if(s[i]!=t[l+r-i])
				{
					f=0;
					break;
				}
			}
			if(f)
			{
				while(l>=0&&r<n)
				{
					if(s[l]!=t[r])break;
					l--;r++;ans++;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}
void change(char *str)
{
    str[0]=str[1]='#';
    for(int i=0; i<n; i++)
    {
        str[i*2+2]=s[i];
        str[i*2+3]='#';
    }
    n=n*2+2;
    str[n]=0;
}
void manacher(char *str,ll *hw)
{
	change(str);
    int maxright=0,mid;
    for(int i=1; i<n; i++)
    {
        if(i<maxright)
            hw[i]=min(hw[(mid<<1)-i],hw[mid]+mid-i);
        else
            hw[i]=1;
        for(; str[i+hw[i]]==str[i-hw[i]]; ++hw[i]);
        if(hw[i]+i>maxright)
        {
            maxright=hw[i]+i;
            mid=i;
        }
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值