Codeforces Round #677 (Div. 3)
F. Zero Remainder Sum
题意
给定一个 \(n \times m\) 的矩阵,你可以在每一行选择不多于 \(\frac{n}{2}\) 个元素, 使得整体选择的元素的和模 \(k\) 为0, 并且和越好。
分析
然后一般情况是第j个数字从j-1的对应转移过来,类似背包。但是这题这么做的话对于模数r的转移对应的要求的是(x%k+a[i][j]%k)%k=r的这个x。
方便一点的话转化成顺推。即考虑第j+1个数字选还是不选。
对于第i行,第j + 1个数字
1、选择第j + 1个数字 dp[i][j + 1][l][(r + a[i][j + 1]) % k] = dp[i][j][l - 1][r] + a[i][j+1];
2、不选择第j + 1个数字 dp[i][j + 1][l][r] = dp[i][j][l][r];
同时这个题注意上下层的关系也要转移过来,因为下一层的状态选择需要上一层的决策。
然后每次更新下一层的第0位,就表示上一层模数为r时的最大值
dp[i + 1][0][0][r] = max(dp[i][j + 1][l][r], dp[i + 1][0][0][r]);
答案就是 dp[n + 1][0][0][0] : 第n+1行,第0位,取0个数字,模数为0的总和
代码
inline int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
const int maxn=80;
int dp[maxn][maxn][maxn][maxn];
int a[maxn][maxn];
main(void)
{
int n=read();
int m=read();
int k=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
a[i][j]=read();
}
memset(dp,-1,sizeof(dp));
dp[1][0][0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
{
for(int cnt=0;cnt<=min(j+1,m/2);cnt++)
{
for(int p=0;p<=k-1;p++)
{
dp[i][j+1][cnt][p]=max(dp[i][j+1][cnt][p],dp[i][j][cnt][p]);
if(cnt>=1&&dp[i][j][cnt-1][p]!=-1)
{
dp[i][j+1][cnt][(p+a[i][j+1])%k]=max(dp[i][j+1][cnt][(p+a[i][j+1])%k],dp[i][j][cnt-1][p]+a[i][j+1]);
}
}
for(int p=0;p<k;p++)
dp[i+1][0][0][p]=max(dp[i+1][0][0][p],dp[i][j+1][cnt][p]);
}
}
}
cout<<max(0,dp[n+1][0][0][0]);
}
G. Reducing Delivery Cost
题意
给定一张 n 个点 m 条边的带权无向图,记 d(u,v) 为 u 到 v 的最短路,给定 k 个点对 (ai,bi)。你可以使任意一条边的边权变为 0,求\(∑d(ai,bi)\)的最小值
思路
思路:开始看到了路线免费,以为是出了分层最短路板子,看了看发现只让一条路免费。
开始直接考虑暴力,枚举每条边,然后每个点进行dijkstra,最后取最小。复杂度O(n^2klogm);
考虑一下对每个点其实可以先预处理,提前处理好每个点的对应的最短路。O(n^2logm)
考虑免费的边的贡献。
对每一个ki来说,边(a,b)有三种情况。
1.开始不在其最短路路径上,免费后也不在其最短路路径上。
2.开始不在其最短路路径上,免费后在其最短路路径上。
3.开始在其最短路路径上,免费后在其最短路路径上。
对于第一种情况,ki的最短路长度不变,仍然是原来的dis[ki.first][ki.second] (ki.first和ki.second分别代表其起点和终点)
对于第二三种情况,ki的最短路长度可能变化,dis[ki.first][a]+dis[ki.second][b];dis[ki.first][b]+dis[ki.second][a];
那么枚举边,再枚举每个k,求出总和的最小.
总时间复杂度O(mk+n^2logm)
代码
#include<bits/stdc++.h>
#define re register
#define inf 1e18
#define int long long
using namespace std;
struct edge
{
int to,cost;
};
vector<edge>g[5005];//定义路径结构体
vector<edge>p[5005];
int n,m,k;
int dis[5005];
int d[1200][1200];
struct node//定义堆结构体
{
//(如果看不懂)https://www.cnblogs.com/ZERO-/p/9347296.html
int u,d;
bool operator<(const node&rhs)const
{
return d>rhs.d;
}
};
inline void djs(int s)
{
for(re int i=1;i<=n;i++)dis[i]=inf;
dis[s]=0;
priority_queue<node>Q;//初始化
node a ={s,0};
Q.push(a);//第一个node
while(!Q.empty())
{
node fr=Q.top();Q.pop();
int u=fr.u,d=fr.d;
//取出并记录
if(d!=dis[u])continue;//避免处理无用数据,也就是dis[u]已经更新,之前未更新数据直接出栈,比如有一组数据 2 5,但是后面又入栈一组数据2 3,则2 5是无用数据
for(re int j=0;j<g[u].size();j++)
{
int tm=g[u][j].to;
if(dis[u]+g[u][j].cost<dis[tm])
{
dis[tm]=dis[u]+g[u][j].cost;
Q.push((node){tm,dis[tm]});
}
}
}
for(int i=1;i<=n;i++)
{
d[i][s]=dis[i];
d[s][i]=dis[i];
// printf("##%d %d %d\n",i,s,d[i][s]);
}
}
inline int read()
{
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
main(void)
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
d[i][j]=inf;
for(int i=1;i<=n;i++)
d[i][i]=0;
int x,y,z;
vector<pair<int,int>>e1,e2;
for (re int i=1;i<=m;i++)
{
edge tmp;
cin>>x>>y>>z;
tmp.cost=z;
tmp.to=y;
g[x].push_back(tmp);
tmp.to=x;
g[y].push_back(tmp);
e1.push_back({x,y});
}
for(int i=1;i<=k;i++)
{
int x=read();
int y=read();
edge tmp;
tmp.to=y;
p[x].push_back(tmp);
e2.push_back({x,y});
}
for(int i=1;i<=n;i++)
{
djs(i);
}
int ans=inf;
for(auto e:e1)
{
int x=e.first,y=e.second;
int index=0;
for(auto pa:e2)
{
int m=pa.first,n=pa.second;
index+=min(d[m][n],min(d[m][x]+d[y][n],d[m][y]+d[x][n]));;
}
ans=min(ans,index);
}
cout<<ans;
}