2022ICPC杭州站

A. Modulo Ruins the Legend

题目链接:Problem - A - Codeforces

样例1输入:

6 24
1 1 4 5 1 4

 样例1输出:

1
0 5

样例2输入:

7 29
1 9 1 9 8 1 0

样例2输出:

0
0 0

题意:给你一个长度为n的数组a[],现在让找到一个长度为n的等差序列b[],使得\sum_{i=1}^{n}(a[i]+b[i])对m取余后的结果尽可能小。输出等差序列的首项s和公差d。

分析:由于我们是想要使得a数组和b数组的和对m的取余结果尽可能小,而a数组的和是已知的,不妨设为sum,b数组的和可以用首项和公差直接表示出来,就是n*s+n*(n+1)/2*d.那么我们就是要使得sum+n*s+n*(n+1)/2*d对m的取余结果尽可能较小。我们发现只有等式n*s+n*(n+1)/2*d=k*gcd(n,n*(n+1)/2)有解,这是由于扩展欧几里得可以得到的。不妨假设g=gcd(n,n*(n+1)/2),不妨假设对m取余的结果最小为ans,那么就有等式sum+k2*g=-k1*m+ans,也就是g*k2+m*k1=ans-sum,由于ans是属于区间[0,m-1]的,那么ans-sum就是属于区间[-sum,m-1-sum]的,那么我们就可以在这个区间内找到一个最小的值val使得g*k2+m*k1=val有解,那么我们由扩展欧几里得可以知道val只要是gcd(g,m)的倍数即可,那么不妨假设gcd(g,m)=gg,那么我们就是找到一个最小的l使得l*gg属于区间[-sum,m-1-sum],这是很容易找的,过程就不说了,找到l之后我们就能够得到ans-sum的值,那么ans的值就可以得到了,通过等式g*k2+m*k1=ans-sum我们可以反解出k1和k2,那么我们将k1代入到等式sum+n*s+n*(n+1)/2*d=-k1*m+ans可以得到n*s+n*(n+1)/2*d=ans-k1*m-sum,那么我们就可以直接用扩展欧几里得直接反解出s和d,但是s和d可能不在区间[0,m-1]中,因为s增加m就会使得总和增加m*n,d增加m就会使得总和增加n*(n+1)/2*m,所以不会影响答案,所以可以直接将s和d对m取余。

 代码:(注意开__int128)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
ll gcd(ll x,ll y)
{
	if(!x) return y;
	return gcd(y%x,x);
}
ll exgcd(ll a,ll b,__int128 &x,__int128 &y)
{
    if(a==0)
    {
        x=0;y=1;
        return b;
    }
    ll d=exgcd(b%a,a,y,x);
    x-=b/a*y;
    return d;
}
int main()
{
	ll n,m,t,sum=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&t);
		sum+=t;
	}
	ll g=gcd(n,n*(n+1)/2);
	ll gg=gcd(g,m);
	ll l=-sum/gg;
	ll ans_sum=l*gg;
	ll ans=ans_sum+sum;
	__int128 k1,k2;
	exgcd(m,g,k1,k2);
	k1*=l;
	k2*=l;
	__int128 s,d;
	exgcd(n,n*(n+1)/2,s,d);
	s*=(ans_sum-k1*m)/g;
	d*=(ans_sum-k1*m)/g;
	s=(s%m+m)%m;
	d=(d%m+m)%m;
	printf("%lld\n",(ll)ans);
	printf("%lld %lld\n",(ll)s,(ll)d);
	return 0;
}

C. No Bug No Game

题目链接:Problem - C - Codeforces

样例输入: 

4 5
2 1 3
2 1 1
2 3 1
2 1 3

样例输出:

9

题意:有n个物品,背包容量为k,每个物品重量为pi,取的重量不同,获得的价值也不同,从1到pi分别为wij,如果当前背包容量足够,则必须取完整的重量,否则才可以取部分重量来填满剩余的背包容量,问能取得的最大价值是多少?

分析:通过思考我们可以发现一个特点就是最多只能有一个物品取部分价值,因为一个物品取部分价值的前提是当前背包剩余容量是不能把当前物品全部装下的,那么取完这个物品后背包就装满了,无法再容纳其余物品了,所以我们可以直接枚举哪个物品会被取部分价值,然后再枚举取的价值,那么除了装部分物品占用的体积外,其余的体积装的货物都是完全装入的,不存在部分的问题,所以也就是一个背包问题,但是我们不可能每次考虑一个物品取部分价值时都重新用背包处理一遍其余物品所能获得的最大价值,所以我们考虑使用前后缀背包优化,这个时候加入我们取第i个物品的体积为j,那么剩余的背包体积就是k-j,我们可以枚举前i-1个物品所占用的体积p,那么i+1个物品及其之后所占用的体积就是k-j-p,而这两部分我们都可以事先通过背包预处理出来,所以总的复杂度就是O(n*10*k).

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=3e3+10;
int f[2][N][N];
int p[N];
vector<int> v[N];
int main()
{
	int n,m;
	cin>>n>>m;
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&p[i]);
		sum+=p[i];
		for(int j=1;j<=p[i];j++)
		{
			int t;
			scanf("%d",&t);
			v[i].push_back(t);
		}
	}
	if(sum<m)
	{
		int ans=0;
		for(int i=1;i<=n;i++)
			ans+=v[i].back();
		printf("%d\n",ans);
		return 0;
	}
	memset(f,-1,sizeof f);
	f[0][0][0]=0;
	f[1][n+1][0]=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		f[0][i][j]=f[0][i-1][j];
		if(j>=p[i]&&f[0][i-1][j-p[i]]!=-1)
			f[0][i][j]=max(f[0][i][j],f[0][i-1][j-p[i]]+v[i].back());
	}
	for(int i=n;i>=1;i--)
	for(int j=0;j<=m;j++)
	{
		f[1][i][j]=f[1][i+1][j];
		if(j>=p[i]&&f[1][i+1][j-p[i]]!=-1)
			f[1][i][j]=max(f[1][i][j],f[1][i+1][j-p[i]]+v[i].back());
	}
	int ans=0;
	for(int i=1;i<=n;i++)//枚举选取部分的商品
	for(int j=1;j<=p[i];j++)//枚举选取的部分 
	{
		int res=m-j;//剩余部分 
		for(int l=0;l<=res;l++)//枚举左半部分商品的
		{
			int r=res-l;
			if(f[0][i-1][l]!=-1&&f[1][i+1][r]!=-1)
				ans=max(ans,v[i][j-1]+f[0][i-1][l]+f[1][i+1][r]);
		}
	}
	printf("%d\n",ans);
	return 0;
} 

D. Money Game

题目链接:Problem - D - Codeforces

样例输入:

2
4 2

样例输出:

4.00 2.00

题意:给你一个长度为n的数组a,在每轮循环会依次执行下列操作:

a[2]+=0.5*a[1]

a[3]+=0.5*a[2]

……

a[n]+=0.5*a[n-1]

a[1]+=0.5*a[n]

现在问经过2022的1204次方轮循环后的数组的值是多少

分析:容易发现,每轮循环后数组的和是不变的,而且只有当a[1]=2*a[2],且a[2]=a[3]=……=a[n],时数组就会固定,而给定的操作次数近似于无穷大,所以数组会趋于稳定,那么不妨设加和为sum,那么a[1]=2/(n+1)*sum,而a[2]=a[3]=……=a[n]=sum/(n+1).

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
	int n;
	cin>>n;
	long long sum=0;
	for(int i=1;i<=n;i++)
	{
		long long t;
		scanf("%lld",&t);
		sum+=t;
	}
	printf("%.12lf",2.0*sum/(n+1));
	for(int i=2;i<=n;i++)
		printf(" %.12lf",1.0*sum/(n+1));
	return 0;
}

F. Da Mi Lao Shi Ai Kan De

题目链接:Problem - F - Codeforces

样例输入: 

6
1
biebie
1
adwlknafdoaihfawofd
3
ap
ql
biebie
2
pbpbpbpbpbpbpbpb
bbbbbbbbbbie
0
3
abie
bbie
cbie

样例输出:

biebie
Time to play Genshin Impact, Teacher Rice!
Time to play Genshin Impact, Teacher Rice!
bbbbbbbbbbie
Time to play Genshin Impact, Teacher Rice!
abie
bbie
cbie

题意:有n组字符串每组中有m个字符串,要求输出含有"bie"的字符串,同一字符串只输出第一次,如果一组字符串中都没有能输出的字符串,则输出"Time to play Genshin Impact, Teacher Rice!"

分析:这个是签到题,直接模拟即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
string s;
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int T;
	cin>>T;
	map<string,int> mp;
	while(T--)
	{
		int n;
		cin>>n;
		bool flag=false;
		for(int i=1;i<=n;i++)
		{
			cin>>s;
			int len=s.size();
			for(int j=0;j+2<len;j++)
			{
				if(s[j]=='b'&&s[j+1]=='i'&&s[j+2]=='e')
				{
					if(mp[s]) break;
					mp[s]=1;
					flag=true;
					cout<<s<<"\n";
					break;
				}
			}
		}
		if(!flag) cout<<"Time to play Genshin Impact, Teacher Rice!\n";
	}
	return 0;
}

K. Master of Both

题目链接:Problem - K - Codeforces

样例输入: 

5 3
aac
oiputata
aaa
suikabudada
aba
abcdefghijklmnopqrstuvwxyz
qwertyuiopasdfghjklzxcvbnm
aquickbrownfxjmpsvethlzydg

样例输出:

4
3
4

题意:给定n个字符串,然后给定m种字典序规则,在这m种字典序规则下求出这n个字符串的逆序对数目。

分析:发现m很大,所以我们没法对每一种排序规则都去求一遍逆序对数目。显然是要先预处理出来一些东西用来求解逆序对数目。我们判断两个字符串的字典序大小关系可以发现他们的字典序大小关系只取决于一对字母的字典序大小关系,不妨我们现在要判断字符串s1和字符串s2的字典序大小关系,那么假设他们前i个字符是相同的,第i+1个字符是不同的,那么无论他们前i个字符的字典序规则是怎样的,都无法直接判断出这两个字符串的字典序大小关系,只有第i+1个字符的字典序关系才能决定这两个字典序的大小关系。可以发现两个字符的对应关系最多有26*26种,所以我们可以预处理出来每种对应关系从而对于每种规则我们只需要统计一下每种关系增加的逆序对数量即可。具体是什么意思呢,不妨假设cnt[a][b]代表由字符对(a,b)影响的且a所在字符串的编号小于b所在字符串的编号的字符串对数目,那么一旦在新的字典序规则下a的字典序大于b的字典序,那么(a,b)就会为答案贡献出cnt[a][b],这都是由这对字符所产生的贡献,我们按照这样的方法统计一下即可得到每种字符排序规则下的逆序对数目。怎么统计cnt数组呢,这个可以在建tire树的时候统计,加入当前字符串,则对于当前字符串的每一位都可以遍历其他字母,这个时候其他字母都是在当前字符之前出现的,而且出现次数已知,所以我们就可以用这个直接更新。

有一点需要注意,由于可能会出现字符串  abc和字符串abcd比较字典序的情况,所以我们可以给每个字符串后面加一个字典序比较小的字符,这里用‘a’-1来表示,然后就可以正常求解了。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e6+10;
int son[N][30],idx,sum[N][30];
long long cnt[1003];
char s[N];
void insert()
{
	int p=0;
	for(int i=0;s[i];i++)
	{
		int k=s[i]-'a'+1;
		if(!son[p][k]) son[p][k]=++idx;
		for(int j=0;j<27;j++)
			cnt[(j+1)*27+k+1]+=sum[p][j];
		sum[p][k]++;
		p=son[p][k];
	}
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		int len=strlen(s);
		s[len]='a'-1;//往末尾添加一个小于小写字母的字符
		s[len+1]=0;
		insert();
	}
	int pos[30];
	while(m--)
	{
		scanf("%s",s+1);
		s[0]='a'-1;
		for(int i=0;i<27;i++)
			pos[s[i]-'a'+1]=i;
		long long ans=0;
		for(int i=0;i<27;i++)
		for(int j=0;j<27;j++)
			if(pos[i]>pos[j])//i的字典序大于j的字典序,那么i在j前面出现多少次就会增加多少逆序对
				ans+=cnt[(i+1)*27+j+1];
		printf("%lld\n",ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值