2022 杭州站 ICPC A C 线性同余方程 dp

2022 ICPC 杭州站 A C 线性同余方程 dp

题目链接
A题:

输入 n n n m m m数组a1到n

我们简化一下题意,给你一个sum(a1到an的和), n n n n ( n + 1 ) / 2 n(n+1)/2 nn+1/2

我们用op1来表示n,用op2来表示 n ( n + 1 ) / 2 n(n+1)/2 nn+1/2

让你找两个数x和y,使

( s u m + x ∗ o p 1 + y ∗ o p 2 ) % m = = a n s (sum+x*op1+y*op2)\%m==ans sum+xop1+yop2%m==ans

a n s ans ans最小,然后输出 a n s ans ans x , y x,y xy的值

实际上就是在mod m的情况下,如果让sum变的最小

在不考虑mod m的情况下,op1和op2的gcd我们可以求出来,用扩展欧几里得

然后我们保存着构成一个gcd的x和y的值

在mod m的情况下,让上面求的gcd和m再扩展欧几里得一次,获得一个更小的gcd

然后这个gcd就用了xx2个op1,yx2个op2

我们获取最小的这个gcd之后,就可以设法让sum对m取模的值最小

对于m取模的值,给它加上m-1产生的效果就是使这个值减一

加上m-k实际上就是减k

当然是在sum不会变成0 的情况下

然后我们就考虑获取能减的最小的数

这个最小的数就是m%op(op就是gcd)

然后每产生一个让sum减去m%op的效果,我们就要给sum加上m%op个op

然后再计算sum最多能被减多少次m%op

sum减去m%op这个数乘以最多减去的数量就是调用op的次数

然后调用一次op要x个op1和y个op2

全部乘一块,就可以构造出最小的ans

然后x的数量和y的数量要在0到m之间,我们对m取个模就不会出现负数

然后需要注意的是m%op如果是0的话,那么最小能被减的数就是op,剩一个就行了

最后注意一下不可被减的情况,特判一下就好了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n,m,k;
int a[N];
int exgcd(int n,int m,int &x,int &y){
	if(!m){
		x=1,y=0; return n;
	}
	int op=exgcd(m,n%m,y,x);
	y-=(n/m)*x;
	return op;
}
void solve()
{
	cin>>n>>k;
	int sum=0;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		sum+=a[i];
	}
	sum%=k;
	int op1=n,op2=(n+1)*n/2;
	op1%=k; op2%=k;
	int x,y,xx,yy;
	int op=exgcd(op1,op2,x,y);
	int po=exgcd(op,k,xx,yy);
	x*=xx; y*=xx; op=po;
	x%=k; y%=k;
	if(op==0){
		cout<<sum<<endl;
		cout<<0<<" "<<0<<endl;
		return;
	}
	int jian=k%op;  if(jian==0) jian=op;//能减多少
	int jianshu=(k-1)/op;//多少次
	int shu=sum/jian;//多少次的多少次
	if(shu==0||jianshu==0){
		cout<<sum%k<<endl;
		cout<<0<<" "<<0<<endl;
		return;
	}
	int ans=sum-jian*shu;
	int ci=jianshu*shu%k;
	cout<<ans<<endl;
	x*=ci; y*=ci; x%=k; y%=k;
	cout<<(x%k+k)%k<<" "<<(y%k+k)%k<<endl;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int test = 1;
	//cin >> test;
	for (int i = 1; i <= test; i++)
	{
		solve();
	}
	return 0;
}

C :
你的能量值为m

然后这个m的作用是,给你n行数,你选择第几个就要消耗多少能量值

你不能吝啬能量值,对于一行,除非你不够,否则就得选最后一个

我们考虑一下 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示看了前i行,用了j个能量所获得的最大力量

然后你会发现处理不了最后一次用,能量值不够这一行的数量的情况

那么我们可以再开一维

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]

依旧是看了前i行,用了j个能量所获得的最大力量,然后k只有两种情况,1和0

1表示着没有遇到最后一次使用能量值,能量值小于本行数量的情况

0表示着遇到了这种情况

那么dp方程很好表示,没有遇到的只能从没有遇到的转移

遇到的可以从遇到的以及没遇到的来转移(这一行是满的,前面不是满的以及前面都是满的,这一行不是满的)

然后把数组初始值调小一点,我们取dp[n][m][0]以及所有dp[i][j][1]的最大值就可以了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e3 + 10;
const int INF =-1e10;
int a[N][N];
int f[N][N][2];
int p[N];
void solve()
{
	int ans=INF;
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> p[i];
        for (int j = 1; j <= p[i]; j++)
        {
            cin >> a[i][j];
        }
    } // 1是有中间的情况
    for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) f[i][j][0]=f[i][j][1]=INF;
    for(int i=0;i<=n;i++) f[i][0][1]=0;
    for (int i = 1; i <= n; i++)
    { // 前i个数
        for (int j = 1; j <= m; j++)
        { // 用了j个能量
            f[i][j][0]=f[i-1][j][0]; f[i][j][1]=f[i-1][j][1];
            if (j >= p[i])    f[i][j][1] = max(f[i][j][1], f[i - 1][j - p[i]][1]+a[i][p[i]]);
            for (int k = 1; k < p[i]; k++)
            {
                if(j>=k)  f[i][j][0] = max(f[i-1][j - k][1] + a[i][k], f[i][j][0]);
            }
            if (j >= p[i])
            f[i][j][0] = max(f[i][j][0], f[i-1][j - p[i]][0] + a[i][p[i]]);
            ans=max(ans,f[i][j][1]);
        }
    }
	ans=max(ans, f[n][m][1]);
	ans=max(f[n][m][0], ans);
	cout<<max(ans,0ll)<<endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int test = 1;
    // cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}

 
 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值