变换序列
题目:
分析:
二分图匹配。
至于字典序最小,从后往前进行匹配即可
程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <memory.h>
#include <fstream>
using namespace std;
const int Max_N = 10010;
int n;
int edge[Max_N][2];
void Init()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++)
{
int x;
scanf("%d", &x);
edge[i][0] = (i + x) % n;
edge[i][1] = (i + n - x) % n;
if(edge[i][0] > edge[i][1])
swap(edge[i][0], edge[i][1]);
}
}
int flag[Max_N];
bool visited[Max_N];
bool Dfs(int x)
{
for(int i = 0; i < 2; i ++)
{
int y = edge[x][i];
if(! visited[y])
{
visited[y] = true;
if(flag[y] == -1 || Dfs(flag[y]))
{
flag[y] = x;
return true;
}
}
}
return false;
}
int res[Max_N];
void Output()
{
for(int i = 0; i < n; i ++)
res[flag[i]] = i;
for(int i = 0; i < n - 1; i ++)
printf("%d ", res[i]);
printf("%d\n", res[n - 1]);
}
void Solve()
{
memset(flag, -1, sizeof(flag));
int cnt = 0;
for(int i = n - 1; i >= 0; i --)
{
memset(visited, false, sizeof(visited));
if(Dfs(i))
cnt ++;
}
if(cnt < n)
printf("No Answer\n");
else
Output();
}
int main()
{
// freopen("transform.in", "r", stdin);
// freopen("transform.out", "w", stdout);
Init();
Solve();
return 0;
}
诗人小G
waiting...
二叉查找树
题目:
分析:
动归题
分析题目可以知道,由于只能更改权值,而数据值大小不变,且整棵树满足儿子节点的数据值大于父节点的数据值。因此,不论权值如何修改,整棵树从左到右的排列顺序不会改变。
于是,
(1)将给定的节点先按权值排序,并进行离散化。然后按数据值排序。
(2)设 dp[ i, j, k ] 表示排序后可用来表示这颗树的数列在区间 [ i, j ] 中,所有权值都大于 k 的最小访问代价。
易知,dp [ i, j, k ] = min (dp[ i, u - 1, k ] + dp[ u + 1, j, k ] + K + sum [ j ] - sum [ i - 1 ],dp[ i, u - 1, k ] + dp[ u + 1, j, k ] + sum[ j ] - sum[ i - 1 ])
其中,u 表示所枚举出的在区间 [ i, j ] 中的根。sum [ i ] 表示 1~ i 的频度总和
前一种表示 u 这个节点的权值小于 k,因此,需要调整。
后一种表示 u 这个节点的权值大于 k,因此,不需要调整。
因为树的访问代价包括 sigma ( 频度 X 深度 )。又因为 dp 是由下向上一层层更新,这样每次加上 sum [ j ] - sum [ i - 1],可以保证 sigma 的正确性
程序
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <memory.h>
using namespace std;
#define LL long long
const int Max_N = 80;
const int inf = 987654321;
struct Tree
{
int data, weight, freq;
}tree[Max_N];
int n, ext;
int sum[Max_N];
bool Cmp1(Tree a, Tree b)
{
return a.data < b.data;
}
bool Cmp2(Tree a, Tree b)
{
return a.weight < b.weight;
}
void Init()
{
scanf("%d%d", &n, &ext);
for(int i = 1; i <= n; i ++)
scanf("%d", &tree[i].data);
for(int i = 1; i <= n; i ++)
scanf("%d", &tree[i].weight);
for(int i = 1; i <= n; i ++)
scanf("%d", &tree[i].freq);
sort(tree + 1, tree + n + 1, Cmp2);
for(int i = 1; i <= n; i ++)
tree[i].weight = i;
sort(tree + 1, tree + n + 1, Cmp1);
sum[0] = 0;
for(int i = 1; i <= n; i ++)
sum[i] = sum[i - 1] + tree[i].freq;
}
LL dp[Max_N][Max_N][Max_N];
LL Dp(int i, int j, int k)
{
if(i > j)
dp[i][j][k] = 0;
if(dp[i][j][k] > -1)
return dp[i][j][k];
for(int u = i; u <= j; u ++)
{
if(tree[u].weight >= k)
if(dp[i][j][k] == -1 || dp[i][j][k] > Dp(i, u - 1, tree[u].weight) + Dp(u + 1, j, tree[u].weight))
dp[i][j][k] = Dp(i, u - 1, tree[u].weight) + Dp(u + 1, j, tree[u].weight);
if(dp[i][j][k] == -1 || dp[i][j][k] > Dp(i, u - 1, k) + Dp(u + 1, j, k) + ext)
dp[i][j][k] = Dp(i, u - 1, k) + Dp(u + 1, j, k) + ext;
}
if(dp[i][j][k] > -1)
dp[i][j][k] += sum[j] - sum[i - 1];
return dp[i][j][k];
}
LL Dp()
{
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j < i; j ++)
for(int k = 0; k < Max_N; k ++)
dp[i][j][k] = 0;
}
for(int l = 1; l <= n; l ++)
{
for(int i = 1; i + l - 1 <= n; i ++)
{
int j = i + l - 1;
for(int k = 0; k <= n; k ++)
{
for(int u = i; u <= j; u ++)
{
if(tree[u].weight >= k)
if(dp[i][j][k] == -1 || dp[i][j][k] > Dp(i, u - 1, tree[u].weight) + Dp(u + 1, j, tree[u].weight))
dp[i][j][k] = Dp(i, u - 1, tree[u].weight) + Dp(u + 1, j, tree[u].weight);
if(dp[i][j][k] == -1 || dp[i][j][k] > Dp(i, u - 1, k) + Dp(u + 1, j, k) + ext)
dp[i][j][k] = Dp(i, u - 1, k) + Dp(u + 1, j, k) + ext;
}
if(dp[i][j][k] > -1)
dp[i][j][k] += sum[j] - sum[i - 1];
}
}
}
}
void Solve()
{
memset(dp, -1, sizeof(dp));
// printf("%lld\n", dp[1][n][0]);
printf("%lld\n", Dp(1, n, 0));
}
int main()
{
Init();
Solve();
return 0;
}
植物大战僵尸
题目:
分析:
网络流题:最大闭权子图
(1)我们根据每个植物的保护关系建立一张图。其中,每行中靠右的植物一定保护靠左的植物。
(2)我们发现,如果一些植物构成环。那么这一定是不能被僵尸吃掉的。建图把它们删掉。
(3)由于需要求出的是 score 总和最大。因此,如果把所建的边全部反向,会发现要求答案,只要求出最大闭权子图就可以了。因为闭合图是指图中每个点所指向的点也在图内,如果边没有反向,那么最左边的植物是必选的,但是右侧的点不一定选,与题意不符。而如果反向,那么最右边的植物是必选的,而较左侧的点可以不选,符合题意。
程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <memory.h>
#include <queue>
#include <fstream>
using namespace std;
const int Max_N = 610;
const int inf = ~0U >> 1;
int n, m;
struct Edge
{
int y, flow, next;
}pvz[Max_N * Max_N * 2], edge[Max_N * Max_N * 5];
int len_e, root_e[Max_N];
int deg_in[Max_N], len_p, root_p[Max_N];
int s, t;
int score[Max_N * Max_N];
void Add_Edge_(int x, int y, int z)
{
edge[len_e] = (Edge){y, z, root_e[x]};
root_e[x] = len_e ++;
}
void Add_Edge(int x, int y, int z)
{
Add_Edge_(x, y, z);
Add_Edge_(y, x, 0);
}
void Add_Pvz(int x, int y)//contain the node who points to itself
{
pvz[len_p] = (Edge) {y, 0, root_p[x]};
root_p[x] = len_p ++;
deg_in[y] ++;
}
int Point2Id(int x, int y)
{
return x * m + y;
}
void Make_Pvz()//transposed graph
{
memset(deg_in, 0, sizeof(deg_in));
memset(root_p, -1, sizeof(root_p));
len_p = 0;
for(int i = 0; i < n; i ++)
{
for(int j = 0; j < m; j ++)
{
scanf("%d", &score[Point2Id(i, j)]);
if(j + 1 < m)//(i, j) -> (i, j + 1)
Add_Pvz(Point2Id(i, j + 1), Point2Id(i, j));
// Add_Pvz(Point2Id(i, j), Point2Id(i, j + 1));
int tmp;
scanf("%d", &tmp);
for(int k = 0; k < tmp; k ++)
{
int x, y;
scanf("%d%d", &x, &y);//(x, y) -> (i, j)
Add_Pvz(Point2Id(i, j), Point2Id(x, y));
// Add_Pvz(Point2Id(x, y), Point2Id(i, j));
}
}
}
n = n * m;
}
bool choose[Max_N];
void Topo_Sort()
{
queue <int> q;
memset(choose, false, sizeof(choose));
for(int i = 0; i < n; i ++)
if(deg_in[i] == 0)
q.push(i);
while(!q.empty())
{
int x = q.front();
q.pop();
choose[x] = true;
for(int p = root_p[x]; p != -1; p = pvz[p].next)
{
int y = pvz[p].y;
deg_in[y] --;
if(deg_in[y] == 0)
q.push(y);
}
}
}
int remain;
void Make_Graph()
{
s = n;
t = s + 1;
remain = 0;
len_e = 0;
memset(root_e, -1, sizeof(root_e));
for(int i = 0; i < n; i ++)
{
if(choose[i])
{
if(score[i] > 0)
{
Add_Edge(s, i, score[i]);
remain += score[i];
}
else
Add_Edge(i, t, -score[i]);
for(int p = root_p[i]; p != -1; p = pvz[p].next)
{
int y = pvz[p].y;
if(choose[y])
Add_Edge(y, i, inf);
}
}
}
}
int d[Max_N];
int seq[Max_N], l, r;
bool Bfs()
{
memset(d, 0, sizeof(d));
l = r = 0;
seq[r ++] = s;
d[s] = 1;
while(l < r)
{
int i = seq[l ++];
for(int p = root_e[i]; p != -1; p = edge[p].next)
{
int y = edge[p].y;
if(!d[y] && edge[p].flow > 0)
{
d[y] = d[i] + 1;
seq[r ++] = y;
if(y == t)
return true;
}
}
}
return false;
}
int Dinic(int i, int flow)
{
if(i == t)
return flow;
int res = flow;
for(int p = root_e[i]; p != -1; p = edge[p].next)
if(edge[p].flow > 0 && d[i] + 1 == d[edge[p].y])
{
int tmp = Dinic(edge[p].y, min(res, edge[p].flow));
if(tmp == 0)
d[edge[p].y] = 0;
edge[p].flow -= tmp;
edge[p ^ 1].flow += tmp;
res -= tmp;
}
return flow - res;
}
void Init()
{
scanf("%d%d", &n, &m);
s = n * m;
t = s + 1;
Make_Pvz();
Topo_Sort();
Make_Graph();
}
void Solve()
{
int ans = 0;
while(Bfs())
ans += Dinic(s, inf);
printf("%d\n", remain - ans);
}
int main()
{
/*
freopen("pvz.in", "r", stdin);
freopen("pvz.out", "w", stdout);
*/
Init();
Solve();
return 0;
}
管道取珠
题目:
分析:
组合数学
设 f [ x1, y1, x2, y2 ] 表示分别采用两种方法X,Y同时得到相同的珠子的排列的个数。对于每一个结果A,它有 |X| * |Y| 中可能性。其中 |X| = |Y| = a [ i ]
因此,sigma (ai ^ 2) 就表示采用两种方法,经过各种步骤达到最终结果的总和
程序:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <memory.h>
using namespace std;
const int Max_N = 510;
const int Mod = 1024523;
int n_up, n_down;
char sign_up[Max_N], sign_down[Max_N];
void Init()
{
scanf("%d%d", &n_up, &n_down);
scanf("%s%s", sign_up + 1, sign_down + 1);
}
int dp[Max_N][Max_N][Max_N];
void Add(int i1, int j1, int i2, int delta)
{
dp[i1][j1][i2] = (dp[i1][j1][i2] + delta) % Mod;
}
void Solve()
{
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
for(int t = 0; t < n_up + n_down; t ++)
{
for(int i1 = 0; i1 <= t; i1 ++)
{
if(i1 > n_up)
break;
int j1 = t - i1;
for(int i2 = 0; i2 <= t; i2 ++)
{
if(i2 > n_up)
break;
int j2 = t - i2;
int delta = dp[i1][j1][i2];
if(!delta)
continue;
if(sign_up[i1 + 1] == sign_up[i2 + 1])
Add(i1 + 1, j1, i2 + 1, delta);
if(sign_up[i1 + 1] == sign_down[j2 + 1])
Add(i1 + 1, j1, i2, delta);
if(sign_down[j1 + 1] == sign_up[i2 + 1])
Add(i1, j1 + 1, i2 + 1, delta);
if(sign_down[j1 + 1] == sign_down[j2 + 1])
Add(i1, j1 + 1, i2, delta);
}
}
}
printf("%d\n", dp[n_up][n_down][n_up]);
}
int main()
{
Init();
Solve();
return 0;
}