G2023选拔赛day1 矩阵优化状压DP+最短路Dijkstra+斜率优化


蒋永神又AK老,把我吊起打

T1.P1357 花园

1.1.题目

传送门

大致题意: 小 L 有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为 1n1 \sim n。花园 11nn 是相邻的。他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则:任意相邻 mm 个花圃中都只有不超过 kk 个 C 形的花圃,其余花圃均为
P 形的花圃。
例如,若 n=10,m=5,k=3n=10 , m=5 , k=3 ,则
CCPCPPPPCC 是一种不符合规则的花圃。
CCPPPPCPCP 是一种符合规则的花圃。
请帮小 L 求出符合规则的花园种数对 109+710^9+7取模的结果。
2n10152mmin(n,5),1k<m2 \leq n \le 10^{15},2 \leq m\leq \min(n, 5),1 \leq k \lt m

1.2.题解

拿到这道题目,当时我一看,nn这么大,就离谱,肯定不是什么一般的DP了。然后发现m最多只有55这么大,于是我就直接往矩阵优化和状压的方向想。首先想到了一个O(n×2m)O(n×2^m)的状压思路,定义dp[i][j]dp[i][j]表示到了位置ii,从ii开始,前面mm个花圃的状态为jj的方案数,先处理出mm位置所有合法的花圃状态,由于所有的花圃是一个环,所以第1n1~n个花圃就是第n+1n+mn+1 \sim n+m个花圃,所以我们求的答案就是一个合法状态转移nn次,转移回原状态的方案数之和
于是就有方程式:
dp[i][j]+=dp[i][j>>1]+dp[i][(j>>1)(1<<m1)]dp[i][j] += dp[i][j >> 1] + dp[i][(j>>1)|(1<<m-1)]
然后考虑矩阵加速,构造一个2m×2m2^m×2^m的矩阵,每行2m2^m个状态,每列也是,对于第ii行,将所有能转移到状态i(0i2m1)i(0\leq i \leq 2^m - 1)的状态附为1,然后直接将这个矩阵快速幂(n1)(n-1)次方,最后将能转移会状态ii的方案相加,也就是jz[i][i]jz[i][i]
可惜考试的时候推出了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决定消灭月亮神树。
月亮神树的构造非常神奇,它的枝杈交错纵横,树上甚至存在环路,可以视为一个无向带权连通图的结构。月亮神树有一个核心节点,记为
。要想消灭月亮神树,必须找到月亮神树的严格最不科学生成树,这是它的弱点,这样就能将其一举摧毁。
定义:一个图GG的不科学生成树是GG的一棵子树,在这棵子树上,从核心节点ss到任意一个节点uu的最短路径长度,和在原图上是等长的。其中节点ss是月亮神树的核心节点。
定义:一个图GG的最不科学生成树是GG的所有不科学生成树中,边权和最小的一棵树。
定义:一个图GG的严格最不科学生成树是GG的所有最不科学生成树中,有序边序列的字典序最小的一棵树。
定义:一个图GG的有序边序列是指,将图上所有边按编号从小到大排序后得到的编号序列。当然,子图子树也适用。
现在给定月亮神树,即给定一个nn个点mm条边的无向带权连通图,点的编号为
1,2...n1,2...n,边的编号为1,2...m1,2...m,给定核心节点的编号ss,求其严格最不科学生成树。 1n,m3105,1wi1091\leq n,m \leq3·10^5,1 \leq w_i \leq10^9

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.题目

传送门

大致题意:
僵尸从唯一一条笔直道路接近,你们需要在铭铭的房门前放置植物攻击僵尸,避免僵尸碰到房子。第一关,一只血量为a1a_1点的僵尸从距离房子x1x_1米处速接近,你们放置了攻击力为y1y_1点/秒的植物进行防御;第二关,在上一关基础上,僵尸队列排头增加一只血量为a2a_2点的僵尸,与后一只僵尸距离dd米,从距离房x2x_2米处匀速接近,你们重新放置攻击力为y2y_2点/秒的植物;……;第nn关,僵尸队列共有nn只僵尸,相邻两只僵尸距离dd米,排头僵尸血量为ana_n点,排第二的僵尸血量an1a_{n-1},以此类推,排头僵尸从距离房子xnx_n米处匀速接近,其余僵尸跟随排头同时接近,你们重新放置攻击力为yny_n 点/秒的植物。每只僵尸直线移动速度均为11米/秒,由于植物射击速度远大于僵尸移动速度,可忽略植物子弹在空中的时间。所有僵尸同时出现并接近,因此当一只僵尸死亡后,下一只僵尸立刻开始受到植物子弹的伤害。
游戏得分取决于你们放置的植物攻击力的总和i=1nyi\sum \limits _{i=1} ^{n}y_i,和越小分数越高,为了追求分数上界,你们每关都要放置攻击力尽量小的植物。求这个最小的攻击力总和

3.2.题解

我太菜了,这道题考场上连DP式都没想到,后来才发现,好简单,直接跟着样例解释顺推就推出来了,QWQ。
话不多说。从题目中可以发现植物对于僵尸是一直持续伤害的,所以何不把僵尸都放在一起,那么我们的DP方程式就是:
ans=max(sum[i]sum[j1]x[i]+d(ij))sum[i]=j=1ia[j]ans = max(\frac {sum[i]-sum[j-1]}{x[i]+d*(i-j)})\qquad sum[i]=\sum^{i}_{j=1}a[j]
诶,好像可以斜率优化也。
我们可以ans将其分为两个坐标的形式:
(x[i]+di,sum[i]),(dj,sum[j1])(x[i]+d*i, sum[i]),(d*j,sum[j-1])
那么ans就是他们的斜率。考虑枚举ii,可以发现(x[i]+di,sum[i])(x[i]+d*i, sum[i])是固定不变的,于是对于(dj,sum[j1])(d*j,sum[j-1])我们维护一个下凸包, 为什么呢,我也不知道。
还是我太弱了

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!

Thanks!

posted on 2020-08-25 19:33  PI_PJW  阅读(76)  评论(0)    收藏  举报

导航