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 n(n+1)/2
我们用op1来表示n,用op2来表示 n ( n + 1 ) / 2 n(n+1)/2 n(n+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+x∗op1+y∗op2)%m==ans
a n s ans ans最小,然后输出 a n s ans ans和 x , y x,y x,y的值
实际上就是在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;
}