G2023选拔赛day1 矩阵优化状压DP+最短路Dijkstra+斜率优化
文章目录
T1.P1357 花园
1.1.题目
大致题意: 小 L 有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为 。花园 和 是相邻的。他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则:任意相邻 个花圃中都只有不超过 个 C 形的花圃,其余花圃均为
P 形的花圃。
例如,若 ,则
CCPCPPPPCC是一种不符合规则的花圃。
CCPPPPCPCP是一种符合规则的花圃。
请帮小 L 求出符合规则的花园种数对 取模的结果。
。
1.2.题解
拿到这道题目,当时我一看,这么大,就离谱,肯定不是什么一般的DP了。然后发现m最多只有这么大,于是我就直接往矩阵优化和状压的方向想。首先想到了一个的状压思路,定义表示到了位置,从开始,前面个花圃的状态为的方案数,先处理出位置所有合法的花圃状态,由于所有的花圃是一个环,所以第个花圃就是第个花圃,所以我们求的答案就是一个合法状态转移次,转移回原状态的方案数之和。
于是就有方程式:
然后考虑矩阵加速,构造一个的矩阵,每行个状态,每列也是,对于第行,将所有能转移到状态的状态附为1,然后直接将这个矩阵快速幂次方,最后将能转移会状态的方案相加,也就是
可惜考试的时候推出了dp式,却把dp写挂了,连80都没拿到,QWQ。
1.3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
const LL mod = 1e9 + 7;
struct node {
LL jz[50][50];
int r, c;
void clear () {memset (jz, 0, sizeof jz);}
node operator * (const node& rhs) const{
node fina;
fina.r = r, fina.c = rhs.c;
fina.clear ();
for (int i = 0; i <= r; i ++)
for (int j = 0; j <= rhs.c; j ++)
for (int z = 0; z <= rhs.r; z ++)
fina.jz[i][j] = (fina.jz[i][j] + jz[i][z] * rhs.jz[z][j] % mod) % mod;
return fina;
}
}f;
LL n, ans;
int m, k, cnt;
void qkpow (node &x, LL y){
node fina;
fina = x;
while (y){
if (y & 1)
fina = fina * x;
x = x * x;
y >>= 1;
}
x = fina;
}
bool check (int x){
int sum = 0;
while (x){
if (x % 2 == 1)
sum ++;
x /= 2;
}
return (sum <= k);
}
int main (){
scanf ("%lld %d %d", &n, &m, &k);
cnt = (1 << m) - 1;
for (int i = 0; i <= cnt; i ++){
if (check (i) && check (i >> 1))
f.jz[i][i >> 1] = 1;
if (check (i) && check ((i >> 1) | (1 << m - 1)))
f.jz[i][(i >> 1) | (1 << m - 1)] = 1;
}
f.r = f.c = cnt;
qkpow (f, n - 1);
for (int i = 0; i <= cnt; i ++)
ans = (ans + f.jz[i][i]) % mod;
printf ("%lld\n", ans);
return 0;
}
T2.1-11G. JM的月亮神树
2.1.题目
题目描述
JM今天过得实在是太惨了,他肥肠不爽,因此他搬出了他祖传的月亮神树。月亮神树的光辉照耀XJTUACM,每分钟都可以从所有被照射的人的手中夺取他1%的财产。JM觉得他很快就能赚得盆满钵满。然而月亮神树不久后突然失控了,不仅不把它夺来的财产交给JM,甚至开始夺取JM的财产,把JM气疯了,赶紧关闭了月亮神树。
因为月亮神树没运行多久就被关掉了,所以大家并没有损失多少,迫于月亮神树的威压,并没有人去理它。可是wzk昨天刚中了彩票,赚了100000000000¥,转眼就被剥夺了很多。因为wzk太rich了,所以他的损失格外大。为了夺回财产,无敌的wzk决定消灭月亮神树。
月亮神树的构造非常神奇,它的枝杈交错纵横,树上甚至存在环路,可以视为一个无向带权连通图的结构。月亮神树有一个核心节点,记为
。要想消灭月亮神树,必须找到月亮神树的严格最不科学生成树,这是它的弱点,这样就能将其一举摧毁。
定义:一个图的不科学生成树是的一棵子树,在这棵子树上,从核心节点到任意一个节点的最短路径长度,和在原图上是等长的。其中节点是月亮神树的核心节点。
定义:一个图的最不科学生成树是的所有不科学生成树中,边权和最小的一棵树。
定义:一个图的严格最不科学生成树是的所有最不科学生成树中,有序边序列的字典序最小的一棵树。
定义:一个图的有序边序列是指,将图上所有边按编号从小到大排序后得到的编号序列。当然,子图子树也适用。
现在给定月亮神树,即给定一个个点条边的无向带权连通图,点的编号为
,边的编号为,给定核心节点的编号,求其严格最不科学生成树。
2.2.题解
这道题目是考场上我唯一做出来的一道题,好像就是原题改编,QWQ。
这数据范围很明显,跑一边Dijkstra堆优化,然后对于每个点如果更新的最短路小于原来到这个点的最短路,就直接将这条边的权值和编号存在这个点上,然后如果等于原来到这个点的最短路,就与原来存的边的权值和编号比较,存更优的那条边,最后将选出来的边的权值和输出,并按编号排序输出编号即可。
2.3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <cstdlib>
using namespace std;
#define M 300005
#define LL long long
const LL INF = 1e15;
struct node {
int v, id;
LL w;
node (){};
node (int V, LL W, int ID){
v = V;
w = W;
id = ID;
}
};
struct edge {
int v;
LL Dis;
edge (){};
edge (int V, LL DIS){
v = V;
Dis = DIS;
}
bool operator < (const edge& rhs) const{
return rhs.Dis < Dis;
}
};
int n, m, s, ans[M], cnt;
LL sum, dis[M];
vector <node> G[M];
pair <LL, int> vc[M];
priority_queue <edge> Q;
void Dijkstra (){
for (int i = 1; i <= n; i ++)
dis[i] = INF;
dis[s] = 0;
Q.push (edge (s, 0));
while (! Q.empty ()){
edge f = Q.top ();
Q.pop ();
if (f.Dis != dis[f.v])
continue;
for (int i = 0; i < G[f.v].size(); i ++){
int tmp = G[f.v][i].v, id = G[f.v][i].id; LL tot = G[f.v][i].w;
if (dis[tmp] > f.Dis + tot){
dis[tmp] = f.Dis + tot;
vc[tmp] = make_pair(tot, id);
Q.push (edge (tmp, dis[tmp]));
}
else if (dis[tmp] == f.Dis + tot){
if (tot < vc[tmp].first)
vc[tmp] = make_pair(tot, id);
else if (tot == vc[tmp].first && id < vc[tmp].second)
vc[tmp] = make_pair(tot, id);
}
}
}
}
int main (){
//freopen ("moontree.in", "r", stdin);
//freopen ("moontree.out", "w", stdout);
scanf ("%d %d %d", &n, &m, &s);
for (int i = 1; i <= m; i ++){
int u, v; LL w;
scanf ("%d %d %lld", &u, &v, &w);
G[u].push_back (node (v, w, i));
G[v].push_back (node (u, w, i));
}
Dijkstra ();
for (int i = 1; i <= n; i ++){
if (i != s){
sum += vc[i].first;
ans[++ cnt] = vc[i].second;
}
}
sort (ans + 1, ans + 1 + cnt);
printf ("%d %lld\n", n - 1, sum);
for (int i = 1; i <= cnt; i ++)
printf ("%d ", ans[i]);
return 0;
}
T3.[SDOI2013]保护出题人
3.1.题目
大致题意:
僵尸从唯一一条笔直道路接近,你们需要在铭铭的房门前放置植物攻击僵尸,避免僵尸碰到房子。第一关,一只血量为点的僵尸从距离房子米处速接近,你们放置了攻击力为点/秒的植物进行防御;第二关,在上一关基础上,僵尸队列排头增加一只血量为点的僵尸,与后一只僵尸距离米,从距离房米处匀速接近,你们重新放置攻击力为点/秒的植物;……;第关,僵尸队列共有只僵尸,相邻两只僵尸距离米,排头僵尸血量为点,排第二的僵尸血量,以此类推,排头僵尸从距离房子米处匀速接近,其余僵尸跟随排头同时接近,你们重新放置攻击力为 点/秒的植物。每只僵尸直线移动速度均为米/秒,由于植物射击速度远大于僵尸移动速度,可忽略植物子弹在空中的时间。所有僵尸同时出现并接近,因此当一只僵尸死亡后,下一只僵尸立刻开始受到植物子弹的伤害。
游戏得分取决于你们放置的植物攻击力的总和,和越小分数越高,为了追求分数上界,你们每关都要放置攻击力尽量小的植物。求这个最小的攻击力总和
3.2.题解
我太菜了,这道题考场上连DP式都没想到,后来才发现,好简单,直接跟着样例解释顺推就推出来了,QWQ。
话不多说。从题目中可以发现植物对于僵尸是一直持续伤害的,所以何不把僵尸都放在一起,那么我们的DP方程式就是:
诶,好像可以斜率优化也。
我们可以ans将其分为两个坐标的形式:
那么ans就是他们的斜率。考虑枚举,可以发现是固定不变的,于是对于我们维护一个下凸包, 为什么呢,我也不知道。
还是我太弱了
3.3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
#define M 100005
struct node {
double x, y;
}s[M];
int n, top;
double d, x, a, sum[M], ans;
double slope (node a, node b){
return (a.y - b.y) / (a.x - b.x);
}
int main (){
scanf ("%d %lf", &n, &d);
for (int i = 1; i <= n; i ++){
scanf ("%lf %lf", &a, &x);
sum[i] = sum[i - 1] + a;
node tmp = {i * d, sum[i - 1]};
while (top && slope (s[top - 1], s[top]) > slope (s[top], tmp))
top --;
s[++ top] = tmp;
tmp = {x + i * d, sum[i]};
int l = 1, r = top, mid, maxn = 0;
while (l <= r){
mid = (l + r) / 2;
if (slope (tmp, s[mid]) > slope (tmp, s[mid - 1])){
maxn = mid;
l = mid + 1;
}
else
r = mid - 1;
}
ans += slope (tmp, s[maxn]);
}
printf ("%.0f", ans);
return 0;
}
明天day2,Good luck!
浙公网安备 33010602011771号