-
1005.Path
http://acm.hdu.edu.cn/showproblem.php?pid=6582
题目描述:给你一个图,问你将所有最短路都断开的最小花费是多少?将一条边断开的花费等于他的长度。
很经典的最短路去边加网络流的题目,首先跑一遍最短路,将不是最短路上的路径全部去掉,再在新图上跑一遍最大流,最小割就等于最大流。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#define int long long
using namespace std;
const int maxn=1e4+10;
const int INF=0x3f3f3f3f3f3f3f3f;
struct Edge{
int from,to,w;
Edge(int u,int v,int w):from(u),to(v),w(w){}
};
struct HeapNode{
int d,u;
HeapNode(int _d,int _u):d(_d),u(_u){}
bool operator < (const HeapNode &rhs)const{
return d>rhs.d;
}
};
struct Dijkstra{
vector<int>G[maxn];
vector<Edge>edges;
int d[maxn];
int vis[maxn];
int ok[maxn];
void init(){
for(int i=0;i<maxn;i++){
G[i].clear();
}
edges.clear();
}
void add_edges(int u,int v,int w){
edges.push_back(Edge(u,v,w));
int x=edges.size();
G[u].push_back(x-1);
}
bool dijkstra(int s,int t){
memset(vis,0,sizeof(vis));
memset(ok,0,sizeof(ok));
for(int i=0;i<maxn;i++)d[i]=INF;
d[s]=0;
vis[s]=1;
priority_queue<HeapNode>Q;
Q.push(HeapNode(0,s));
while(Q.size()){
HeapNode x=Q.top();Q.pop();
int u=x.u;
for(int i=0;i<G[u].size();i++){
Edge &e=edges[G[u][i]];
if(!vis[e.to]&&d[u]+e.w<d[e.to]){
ok[G[u][i]]++;
d[e.to]=d[u]+e.w;
Q.push(HeapNode(d[e.to],e.to));
vis[e.to]=1;
}
}
}
return vis[t];
}
};
Dijkstra dij;
struct Edge1
{
int from,to,cap,flow;
Edge1(int u,int v,int cap,int flow):from(u),to(v),cap(cap),flow(flow){}
};
struct Dinic
{
int n,m,s,t;
vector<int>G[maxn];
vector<Edge1>edges;
bool vis[maxn];
int d[maxn];
int cur[maxn];
void add_edges(int u,int v,int cap)
{
edges.push_back(Edge1(u,v,cap,0));
edges.push_back(Edge1(v,u,0,0));
m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
void init(int n)
{
this->n=n;
for(int i=0;i<maxn;i++)G[i].clear();
edges.clear();
}
//BFS构造层次图
bool BFS()
{
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
queue<int>Q;
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty())
{
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++)
{
Edge1 &e=edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow)
{
vis[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}
//沿着层次图增广
int DFS(int x,int a)
{
if(x==t||a==0)return a;
int flow=0,f;
for(int &i=cur[x];i<G[x].size();i++)
{
Edge1 &e=edges[G[x][i]];
if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0)
{
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;
}
}
return flow;
}
int Maxflow(int s,int t)
{
this->s=s;
this->t=t;
int flow=0;
while(BFS())
{
memset(cur,0,sizeof(cur));
flow+=DFS(s,INF);
}
return flow;
}
};
Dinic dinic;
int n,m;
signed main(){
int T;
scanf("%lld",&T);
while(T--){
dij.init();
dinic.init(2*n);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
dij.add_edges(u,v,w);
}
if(!dij.dijkstra(1,n)){
puts("0");
break;
}
for(int i=1;i<=n;i++){
for(int j=0;j<dij.G[i].size();j++){
Edge &e=dij.edges[dij.G[i][j]];
if(dij.d[e.to]==dij.d[i]+e.w){
dinic.add_edges(i,e.to,e.w);
}
}
}
printf("%lld\n",dinic.Maxflow(1,n));
}
return 0;
}
/*
3
5 5
1 2 5
2 3 3
3 5 3
4 5 3
2 4 3
3 4
1 2 1
2 3 1
1 3 2
1 3 3
*/
-
1002.Operation
http://acm.hdu.edu.cn/showproblem.php?pid=6579
题目描述:给你初始一个数组,有两种操作:
1.询问[l,r]中区间异或和最大是多少
2.将数组最后加一个数
本题强制在线。
数据范围很大,线段树维护区间线性基必然超时,必须考虑新的方法。
这里粘贴大神博客,有助于加强线性基的理解
https://blog.csdn.net/henu_jizhideqingwa/article/details/96893284
-
1001.Blank
http://acm.hdu.edu.cn/showproblem.php?pid=6578
题目描述:有很多个空格,你要在空格内填数,有0,1,2,3四种数,还有许多约束条件,对于每个区间[l,r],规定只能有x种不同的数。问你最后有多少种满足条件的方案,答案对998244353取模。
答案思路:
定义 dp[i][j][k][t] 代表填完前 t 个位置后,{0, 1, 2, 3} 这 4 个数字最后一次出现的位置,
排序后为 i, j, k, t(i < j < k < t) 的方案数目,则按照第 t + 1 位的数字的四种选择,可以得
到四种转移。
对于限制可以按照限制区间的右端点分类,求出 dp[i][j][k][t] 后,找到所有以 t 为区间
右端点的限制条件,如果当前状态不满足所有限制条件则不合法,不再向后转移。
总时间复杂度 O(n^4)
滚动一维,空间复杂度 O(n^3)
理解:
先附上标程代码:
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, int> piir;
const int N = 100 + 5;
int n, m, ans;
int dp[2][N][N][N];
vector <piir> lims[N];
inline void Mod(int &x) {
static int mod = 998244353;//1e9 + 7;
if (x >= mod) x -= mod;
}
int main() {
int Case, ans, l, r, x;
int i, j, k, t, p;
for (scanf("%d", &Case); Case --; ) {
ans = 0;
scanf("%d %d", &n, &m);
for (i = 1; i <= n; i ++)
lims[i].clear();
for (i = 0; i < m; i ++) {
scanf("%d %d %d", &l, &r, &x);
lims[r].push_back(piir(l, x));
}
dp[0][0][0][0] = 1;
//n的数据范围是1-n,按顺序遍历结尾
for (i = p = 1; i <= n; i ++, p ^= 1) {
//滚动数组的目的就是记录前一次的状态
//数组初始化,滚动数组把当前状态置为0
for (j = 0; j <= i; j ++)
for (k = 0; k <= j; k ++)
for (t = 0; t <= k; t ++)
dp[p][j][k][t] = 0;
for (j = 0; j < i; j ++)
for (k = 0; k <= j; k ++)
for (t = 0; t <= k; t ++) {
//状态转移方程
//当前位置选的数与上一个相同
Mod(dp[p][j][k][t] += dp[p ^ 1][j][k][t]);
//当前位置的上一位为j,记录状态i-1
Mod(dp[p][i - 1][k][t] += dp[p ^ 1][j][k][t]);
//当前位置的上一位为k,,记录状态i-1
Mod(dp[p][i - 1][j][t] += dp[p ^ 1][j][k][t]);
//当前位置的上一位为t,记录状态i-1
Mod(dp[p][i - 1][j][k] += dp[p ^ 1][j][k][t]);
}
for (j = 0; j < i; j ++)
for (k = 0; k <= j; k ++)
for (t = 0; t <= k; t ++)
for (piir tmp : lims[i])
//放在r的位置上肯定合法,只需判断从l到r-1是否合法
if (1 + (j >= tmp.first) + (k >= tmp.first) + (t >= tmp.first) != tmp.second)
dp[p][j][k][t] = 0;
}
for (i = 0, p = n & 1; i < n; i ++)
for (j = 0; j <= i; j ++)
for (k = 0; k <= j; k ++)
Mod(ans += dp[p][i][j][k]);
printf("%d\n", ans);
}
return 0;
}
dp数组滚动时,通常需要记录上一次状态和当前状态,用p代表上一状态,p^1代表前一状态,这样可以少做一维状态的记录。
滚动数组解决之后,我们就得弄懂答案中说的四种转移方程是怎样的
看代码:
Mod(dp[p][j][k][t] += dp[p ^ 1][j][k][t]);
Mod(dp[p][i - 1][k][t] += dp[p ^ 1][j][k][t]);
Mod(dp[p][i - 1][j][t] += dp[p ^ 1][j][k][t]);
Mod(dp[p][i - 1][j][k] += dp[p ^ 1][j][k][t]);
1.当前选择的数与上一个相同,那么上一个状态会被当前状态覆盖,不记录,直接转移
2.当前选择的数的上一个为j,那么j此时不需要记录,而上一个状态则需要记录,所以要加上dp[p^1][j][k][t];
3.当前选择的数的上一个为k,与2同理
4.当前选择的数的上一个为t,与2同理
最后在判断条件是否满足时,因为是首先将右端点r安排数字,所以右端点一定满足条件,直接加上1,再判断从左端点开始的每个数是否满足条件即可。