题目描述:给一个无向无环图, n n n 个点 ( 1 − n ) (1 - n) (1−n) m m m 条边, 总时间为 t t t,一个机器人从点 1 1 1 出发, 三种行为: 停在原地,去下一个相邻的城市,自爆。 问经过了 t t t 秒,机器人的行为方案数是多少 ? ? ?
数据范围
1 < t ≤ 1 0 6 1
0 < M < 100 0
1 ≤ u 1 \leq u 1≤u, v ≤ N v \leq N v≤N
一开始看到这题感觉数据很水,想当然的写了个爆搜看下能过几个点,结果当然是Wa了 一个点没过。
真实思路:
我们先考虑 D p Dp Dp: 令 f [ i , j ] f[i, j] f[i,j] 表示 从点 i i i 走到 点 j j j 的方案数 ,那么 更新方式:
f [ i , j ] + = f [ i , k ] ∗ f [ k ] [ j ] f[i, j] += f[i, k] * f[k][j] f[i,j]+=f[i,k]∗f[k][j]
我们在设置一个点0 连接所有边来表示自爆, 增加自环表示停顿即可。
状态转移代码:
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= n; j ++)
for(int k = 0; k <= n; k ++)
f[i][j] = (f[i][j] + f[i][k] * f[k][j]) % 2017;
但是这样没办法处理走 t t t 步
然后我们发现,这个代码好像就是矩阵的乘法, 一个邻接矩阵 A A A, A k A^k Ak 就可以表示从 i i i 到 j j j 经过 k k k 步的路径方案总数。
然后写一个矩阵的 k k k次方的快速幂即可。
最后统计一下点 1 1 1 到所有点的方案数总和即可
代码;
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
int n, m, t, ans;
typedef struct Mat {
int g[35][35];
Mat() {
memset(g, 0, sizeof g);
}
Mat operator * (const Mat b) const { //矩阵乘法
Mat c;
for(int i = 0; i <= n; i ++)
for(int j = 0; j <= n; j ++)
for(int k = 0; k <= n; k ++)
c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j]) % 2017;
return c;
}
}M;
M I, E, ansm;
M pow(M a,int p) { //快速幂
if(p == 0) return I;
M temp = pow(a, p / 2);
if(p % 2) return temp * temp * a;
else return temp * temp;
}
int main() {
ios::sync_with_stdio; cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n >> m;
for(int i = 1; i <= m; i ++) {
int a, b;
cin >> a >> b;
E.g[a][b] = E.g[b][a] = 1;
}
for(int i = 0; i <= n; i ++) I.g[i][i] = E.g[i][i] = 1, E.g[i][0] = 1; //连接停下来的边和自爆的边
cin >> t;
ansm = pow(E, t);
for(int i = 0; i <= n; i ++) ans += ansm.g[1][i];
cout << ans % 2017 << '\n';
return 0;
}
题目描述:
物流公司要把一批货物从码头 A A A 运到码头 B B B。由于货物量比较大,需要 n n n 天才能运完。货物运输过程中一般要转停好几个码头。
物流公司通常会设计一条固定的运输路线。有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。而修改线路会导致额外 k k k 的花费。 求 n n n天运输的最小花费。
数据范围: 1 ≤ n ≤ 100 , 1 ≤ m ≤ 20 。 1 \le n \le 100,1\le m \le 20。 1≤n≤100,1≤m≤20。
思路:
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 110, M = 20100;
int n, m, k, s;
ll f[N][N];
int e[M], ne[M], w[M], h[N], idx;
int dist[N];
bool st[N], is_use[N][N];
ll dp[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
int dijkstra() {
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII>> hea;
hea.push({0, 1});
dist[1] = 0;
while(hea.size()) {
auto t = hea.top();
hea.pop();
int ver = t.second;
if(st[ver]) continue;
st[ver] = true;
for(int i = h[ver]; ~i; i = ne[i]) {
int j = e[i];
if(st[j]) continue;
if(dist[j] > dist[ver] + w[i]) {
dist[j] = dist[ver] + w[i];
hea.push({dist[j], j});
}
}
}
//cout << dist[5] << '\n';
return dist[m];
}
int main() {
ios::sync_with_stdio; cin.tie(0); cout.tie(0);
cin >> n >> m >> k >> s;
memset(h, -1, sizeof h);
while(s --) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
int d;
cin >> d;
while(d --) {
int a, b, c;
cin >> a >> b >> c;
for(int i = b; i <= c; i ++) is_use[a][i] = true; //表示不能用
}
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++) {
memset(st, 0, sizeof st);
for(int a = 1; a <= m; a ++)
for(int b = i; b <= j; b ++)
if(is_use[a][b]) st[a] = true;
f[i][j] = dijkstra();
}
//dp[i] 表示前i天的最小花费
memset(dp, 0x3f, sizeof dp);
for(int i = 1; i <= n; i ++) {
dp[i] = (ll)f[1][i] * i;
for(int j = i - 1; j >= 0; j --) {
dp[i] = min(dp[i], dp[j] + (ll)(i - j) * f[j + 1][i] + k);
}
}
cout << dp[n] << '\n';
return 0;
}
题目描述: 太麻烦了点击进去自己看。
思路:
记忆化搜索 + + + 树形 D p Dp Dp
f ( i , j , k ) f(i, j, k) f(i,j,k) 表示第 i i i 个点到根经过 j j j 个未被修复的公路, k k k 个未被修复的铁路,所得到的最小值。
状态转移:当更新到乡村的时候:
f [ x ] [ p ] [ q ] = m i n ( d f s ( s o n [ x ] [ 0 ] , p , q ) + d f s ( s o n [ x ] [ 1 ] , p , q + 1 ) , d f s ( s o n [ x ] [ 1 ] , p , q ) + d f s ( s o n [ x ] [ 0 ] , p + 1 , q ) ) f[x][p][q]=min(dfs(son[x][0],p,q)+dfs(son[x][1],p,q+1),dfs(son[x][1],p,q)+dfs(son[x][0],p+1,q)) f[x][p][q]=min(dfs(son[x][0],p,q)+dfs(son[x][1],p,q+1),dfs(son[x][1],p,q)+dfs(son[x][0],p+1,q))
答案就是 f ( 1 , p , q ) f(1, p, q) f(1,p,q) 根节点经过 p p p 条公路和 q q q 条铁路所得到的最小值
这里用 s o n ( x , 1 / 0 ) son(x, 1/0) son(x,1/0) 表示 x x x 的左儿子为公路所连接点, 右儿子为马路所连点。
用 ( 1 (1 (1 ~ n − 1 ) n - 1) n−1) 表示城市 ( n (n (n ~ 2 n − 1 ) 2n - 1) 2n−1) 表示乡村。
代码:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e4 + 10;
int n;
struct Node {
ll a, b, c;
}s[N];
ll f[N][45][45];
int son[N][5]; //0 为公路, 1 为铁路
ll dfs(int x, int p, int q) { //p条公路 q条铁路
if(x >= n) return s[x - n + 1].c * (s[x - n + 1].a + p) * (s[x - n + 1].b + q);
if(f[x][p][q] != f[n + 1][41][41]) return f[x][p][q]; //已经被更新过
return f[x][p][q] = min(dfs(son[x][0], p, q) + dfs(son[x][1], p, q + 1), dfs(son[x][1], p, q) + dfs(son[x][0], p + 1, q));
}
int main() {
ios::sync_with_stdio; cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n;
memset(f, 63, sizeof(f));
for(int i = 1; i < n; i ++) {
int x, y;
cin >> x >> y;
if(x < 0) x = -x + n - 1; //公路
if(y < 0) y = -y + n - 1; //铁路
son[i][0] = x;
son[i][1] = y;
}
for(int i = 1; i <= n; i ++) {
cin >> s[i].a >> s[i].b >> s[i].c;
}
cout << dfs(1, 0, 0) << '\n';
return 0;
}
题目描述:给定一颗树和一个树的根节点,求使得从根节点开始,到每个叶子节点的时间相同需要对每条边增加的边权值的最小值。
数据范围:
N ≤ 500000 N≤500000 N≤500000
思路:
#include
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e6 + 10, M = N * 2;
int n, s;
ll ans;
int e[M], ne[M], w[M], h[N], idx;
int dist[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dfs(int u, int fa) {
for(int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if(j == fa) continue;
dfs(j, u);
dist[u] = max(dist[u], dist[j] + w[i]); //更新子树到根节点的最大距离
//cout << u << ":" << dist[u] << '\n';
}
for(int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
//cout << fa << ": " << j << '\n';
if(j == fa) continue;
ans += (dist[u] - (dist[j] + w[i]));
//cout << j << '\n';
}
//cout << u << ":" << dist[u] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n >> s;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs(s, -1);
cout << ans << '\n';
return 0;
}
【思维题】
题目概述:
两个人玩游戏,每次可以拿走图中一个叶子节点,当有人拿到唯一的特殊节点时就获胜吗,问谁会赢。
思路:
代码:
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
int T;
cin >> T;
while(T --) {
int n, x;
cin >> n >> x;
int cnt = 0;
for(int i = 1; i < n; i ++) {
int a, b;
cin >> a >> b;
if(a == x || b == x) cnt ++;
}
if(cnt == 1 || cnt == 0) cout << "Ayush" << '\n';
else if((n - cnt - 1) % 2){
if(cnt % 2) cout << "Ashish" << '\n';
else cout << "Ayush" << '\n';
} else {
if(cnt % 2) cout << "Ayush" << '\n';
else cout << "Ashish" << '\n';
}
}
return 0;
}
题目概述:
一个竞赛图中找大小为 3 3 3 的环,输出任意一个,如果没有就输出 − 1 -1 −1。
竞赛图:若有向简单图 G G G 满足任意不同两点间都有恰好一条边(单向),则称 G G G 为 竞赛图 ( T o u r n a m e n t g r a p h ) (Tournament graph) (Tournamentgraph)。
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 5010, M = N * N;
int n;
char g[N][N];
int to[N]; //to[i] 存储 i 到达的点
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
cin >> g[i][j];
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
if(g[i][j] == '1' && (!to[i] || g[j][to[i]] == '1')) to[i] = j;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
if(g[j][i] == '1' && g[to[i]][j] == '1') {
cout << i << ' ' << to[i] << ' ' << j << '\n';
return 0;
}
cout << -1 << '\n';
return 0;
}
题目概述:
n n n 个点 m m m 条边,每删一个点需要花费与该点直接相连的点 i i i 的 v [ i ] v[i] v[i],问删完整个图的最小花费。
本题考虑删点过于麻烦,因为要把图删完,所以删边也是一样的效果。
对于删掉的每条边,最小花费是边的两个节点的最小 v [ i ] v[i] v[i],将每条边的最小花费加起来就是最后的答案。
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1010, M = N * 2;
int n, m;
int a[N], ans;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> a[i];
while(m --) {
int x, y;
cin >> x >> y;
ans += min(a[x], a[y]);
}
cout << ans << '\n';
return 0;
}