断网练习 2

Day 31

2466 Sue 的小球

 一遍过捏。

点击查看代码
struct node{
	int x,y,v;
	bool operator < (const node &t) const {
		return x < t.x;
	}
};

int n,st,be;
node w[N],temp[N];
lwl dp[N][N][2];
lwl sum[N];

lwl dis(int i,int j) {
	return abs(w[i].x - w[j].x);
}

int get(int l,int r) {
	return sum[n] - (sum[r] - sum[l - 1]);
}

void mx(lwl &a,lwl b) {
	if (a < b) a = b;
}

int main(){
	lwl qwq = 0;
	n = fr(), st = fr();
	for (int i = 1; i <= n; i ++) w[i].x = fr();
	for (int i = 1; i <= n; i ++) w[i].y = fr(), qwq += w[i].y;
	for (int i = 1; i <= n; i ++) w[i].v = fr();
	w[0].x = -inf;
	sort(w + 1, w + 1 + n);
	for (int i = 1,j = 1; j <= n; i ++, j ++) {
		temp[j] = w[i];
		if (w[i].x > st && w[i - 1].x < st) {
			temp[j] = {st,0,0};
			be = j;
			j ++;
			temp[j] = w[i];
			n ++;
		} else if (w[i].x == st) be = i;
	}
	if (!be) {
		be = ++ n;
		w[n] = {st,0,0};
	}
	for (int i = 1; i <= n; i ++) {
		w[i] = temp[i];
		sum[i] = sum[i - 1] + w[i].v;
	}
	memset(dp,-0x3f,sizeof dp);
	dp[0][0][1] = dp[0][0][0] = qwq;
	for (int len = 1; len < n; len ++) {
		for (int l = 0; l <= len && be - l > 0; l ++) {
			int r = len - l;
			if (be + r > n) continue;
			lwl &t1 = dp[l][r][1], &t0 = dp[l][r][0];
			if (l) {
				lwl a = get(be - l + 1,be + r);
				mx(t0,dp[l - 1][r][0] - dis(be - l,be - l + 1) * a);
				mx(t0,dp[l - 1][r][1] - dis(be - l,be + r) * a);
			}
			if (r) {
				lwl a = get(be - l,be + r - 1);
				mx(t1,dp[l][r - 1][0] - dis(be - l,be + r) * a);
				mx(t1,dp[l][r - 1][1] - dis(be + r,be + r - 1) * a);
			}
		}
	}
	lwl ans = -linf;
	for (int l = 0; l < n; l ++) {
		ans = max(ans,dp[l][n - l - 1][1]);
		ans = max(ans,dp[l][n - l - 1][0]);
	}
	printf("%.3lf",1.0 * ans / 1000);
	return 0;
}

3736 字符合并

 状态压缩加上区间 dp

dp[l][r][h] 表示的是 [l,r] 区间被压缩成状态 h 的最大贡献。

 然后转移的时候,分成两种情况进行转移(或者说有一种情况需要特殊转移)

 普通的转移就是枚举中间的端点(这里的断点需要从右往左转移,不然会重复更新,而且为了不超时,所以是一个区间一个区间一跳的),然后再枚举状态,之后将末尾的 0 或者 1 接到前面一半上面去。

 如果说当前区间的长度正好可以压缩的话,那么就把每一个状态都跑一遍,然后试一试这一个区间是当前状态的时候压缩成 01 哪一个值最大,然后记录一下。然后这里为了避免反复更新,所以用了一个临时记录的而不是直接更新值。

 这题空间好像卡的还挺紧的()

点击查看代码
const int K = (1 << 8) + 5;

int n,k;
int w[N];
int c[K];
int val[K];
int dp[N][N][K];

void mx(int &a, int b) {
	if (a < b) a = b;
}

signed main(){
	n = fr(), k = fr();
	for (int i = 1; i <= n; i ++) w[i] = fr();
	for (int i = 0; i < (1 << k); i ++) {
		c[i] = fr(),val[i] = fr();
	}
	memset(dp,-0x3f,sizeof dp);
	for (int i = 1; i <= n; i ++) dp[i][i][w[i]] = 0;
	for (int len = 2; len <= n; len ++) {
		for (int l = 1; ; l ++) {
			int r = l + len - 1;
			if (r > n) break;
			int t = (len - 1) % (k - 1);
			if (!t) t = k - 1;
			for (int kk = r - 1; kk >= l; kk -= k - 1) {
				for (int h = 0; h < (1 << t); h ++) {
					// 1
					mx(dp[l][r][(h << 1) | 1],dp[l][kk][h] + dp[kk + 1][r][1]);
					// 0
					mx(dp[l][r][h << 1],dp[l][kk][h] + dp[kk + 1][r][0]);
				}
			}
			if (t == k - 1) {
				lwl t0 = -linf, t1 = -linf;
				for (int h = 0; h < (1 << k); h ++) {
					if (c[h]) mx(t1,dp[l][r][h] + val[h]);
					else mx(t0,dp[l][r][h] + val[h]);
				}
				dp[l][r][0] = t0;
				dp[l][r][1] = t1;
			}
		}
	}
	lwl ans = -linf;
	for (int i = 0; i < (1 << k); i ++) 
		mx(ans,dp[1][n][i]);
	fw(ans);
	return 0;
}

5999 kangaroo

 一种之前没有见过的插空 dplink

 就是把元素不停地放到序列或者什么东西中,然后分成 k 段放置,求放置的方案数。状态设置的话就设置成 dp[i][j] 表示前 i 个元素放成 j 段的方案数

 考虑往其中加入一个元素的时候,有以下三种情况:

  1. 加入到一段中,因为可以加入到任意一段中,所以转移是: dp[i][j]+=dp[i1][j]j

  2. 将当前的元素作为新的一段与之前的段放在一起,这个时候这个新的段就是插空放,所以转移是: dp[i][j]+=jdp[i1][j1]

  3. 将当前的元素放在两个段之间,并且合并这两个段,转移: dp[i][j]+=dp[i1][j+1]j

 然后针对这一题来说,其实就相当于求一个排列,让每一个数都比左右两个大或者小,那么联系到这个,我们就可以想到从小到大枚举每一个草丛。然后对应到这三种情况上面去。

  1. 加入到一段中。因为这个加入的元素是单调递增的,所以直接加的话是没有办法做到比两边大或者比两边小的。

  2. 作为新的一段,转移方程就和普通的一样(但是因为起点和终点确定了,要判断一下当前有没有遍历过起点和终点,如果遍历过了要减掉对应的空位)

  3. 合并两个段。因为之前遍历的肯定是比当前元素要小的,所以说这个是可以直接合并的,直接用上面的式子就可以了

 然后如果遍历到了起点和终点要特判一下,因为他们不可以随便插入。

点击查看代码
int n,s,t;
lwl dp[N][N];
// 插空dp  前 i 个数  分成 j 段

int main(){
	n = fr(), s = fr(), t = fr();
	dp[0][0] = 1;
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= i; j ++) {
			if (s == i || t == i) {
				//         合并(另)     新插入
				dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
			} else {
				int w = (i > s) + (i > t);
				// 合并
				dp[i][j] += dp[i - 1][j + 1] * j % mod;
				// 新插入
				dp[i][j] += dp[i - 1][j - 1] * (j - w) % mod;
				dp[i][j] %= mod;
			}
		}
	}
	fw(dp[n][1]);
	return 0;
}

4563 守卫

 这个题目很容易发现的一个性质是如果当前玩耍的区间是 [l,r] 的话,那么 r 是肯定要有一个人看守的,那么在这个基础上我们就枚举 r 然后向前延伸。

 在枚举的时候我们记录一个当前的 r 能够看到的最左边的值,记录为 p ,也就是把这个值作为以前区间 dp 枚举的 k (应该算是贪心吧),最后转移的时候就这个样子转移就可以了

dp[l][r]=dp[p+1][r]+min(dp[l][p1],dp[l][p])

 初始化的话就把所有长度为 1 的区间初始化为 1 就可以了,别忘记算到 ans 里面。判断当前亭子能不能被监视到就用斜率判断就可以啦!

点击查看代码
int n;
int h[N];
int ans;
int dp[N][N];
set<int> s[N];

double get(int i,int j) {
	return (double)(h[j] - h[i]) / (j - i);
}

int main(){
	n = fr();
	for (int i = 1; i <= n; i ++) h[i] = fr();
	// r 一定放
	for (int r = 1; r <= n; r ++) {
		dp[r][r] = 1;
		ans ^= 1;
		double kmn = dinf;
		int p = 0;
		for (int l = r - 1; l; l --) {
			double k = get(l,r);
			if (k < kmn || !p) {
				kmn = k;
				p = l;
			}
			dp[l][r] = dp[p + 1][r] + min(dp[l][p - 1], dp[l][p]);
			ans ^= dp[l][r];
		}
	}
	fw(ans);
	return 0;
}

Day 32

P1879 Corn Fields G

 就是玉米田,然后一开始数组开小了,恼。

点击查看代码
int n,m,qwq,siz;
int flag[N];
int dp[N][M];
vector<int> can;
vector<int> h[M];

bool check(int x) {
	for (int i = 1; i < m; i ++) {
		if ((x >> i) & 1 && (x >> (i - 1)) & 1) 
			return false;
	}
	return true;
}

void init() {
	for (int i = 0; i <= qwq; i ++) {
		if (check(i)) 
			can.push_back(i);
	}
	siz = can.size();
	for (int i = 0; i < siz; i ++) {
		int t = can[i];
		for (auto j : can) {
			if (t & j) continue;
			h[i].push_back(j);
		}
	}
}

int main(){
	n = fr(),m = fr();
	for (int i = 1; i <= n; i ++) {
		for (int j = 0; j < m; j ++) {
			int t = fr();
			t = 1 - t;
			flag[i] += (t << j);
		}
	}
	qwq = (1 << m) - 1;
	init();
	dp[0][0] = 1;
	for (int i = 1; i <= n + 1; i ++) {
		for (int j = 0; j < siz; j ++) {
			int t = can[j];
			if (t & flag[i]) continue;
			for (auto tt : h[j]) {
				dp[i][t] = (dp[i][t] + dp[i - 1][tt]) % mod;
			}
		}
	}
	fw(dp[n + 1][0]);
	return 0;
}

POD-Subdivision of Kingdom

 一开始断网做的时候以为是什么高端的状压 dp ,结果这题是 dfs ,然后再加上可行性剪枝就可以了,被骗啦!

 然后还要预处理一下 1 的个数,这里预处理的时候,只用处理前面一半就可以了,然后计算的时候把两个加起来就可以了。

点击查看代码
int n,m,qwq;
int w[N];
int cnt[1 << 13];
int ans = -inf,res;

int cntt(int i) {
	return cnt[i & qwq] + cnt[i >> 13];
}

void dfs(int u,int u1,int u2,int cnt1,int cnt2,int sum) {
	if (cnt1 == n / 2 && cnt2 == n / 2) {
		if (sum > ans) {
			ans = sum;
			res = u1;
		}
		return ;
	}
	if (cnt1 < n / 2) 
		dfs(u + 1,u1 | (1 << u),u2,cnt1 + 1,cnt2,sum + cntt(w[u] & u1));
	if (cnt2 < n / 2)
		dfs(u + 1,u1,u2 | (1 << u),cnt1,cnt2 + 1,sum + cntt(w[u] & u2));
}

int main(){
	n = fr(), m = fr();
	qwq = (1 << 13) - 1;
	while (m --) {
		int a = fr() - 1, b = fr() - 1;
		w[a] |= (1 << b);
		w[b] |= (1 << a);
	}
	cnt[1] = 1;
	for (int i = 2; i <= qwq; i ++) {
		int t = i;
		while (t) {
			t -= t & -t;
			cnt[i] ++;
		}
	}
	dfs(1,1,0,1,0,0);
	for (int i = 0; i < n; i ++) {
		if (res & (1 << i)) {
			fw(i + 1);
			kg;
		}
	}
	return 0;
}

3869 宝藏

 是一个用了状压表达状态的 bfs ,一遍过捏。

点击查看代码
struct node{
	int x1,y1,x2,y2;
}h[N];

struct nodee{
	int x,y,type;
};

pii S,T;
int n,m,k;
bool flag[N][N];
bool temp[M][N][N];
int ans = inf;
int dis[N][N][M];
vector<int> w[N * N];
int dx[4] = {0,1,0,-1};
int dy[4] = {1,0,-1,0};

int get(int i,int j) {
	return (i - 1) * m + j;
}

pii zb(int w) {
	int t = w % m;
	if (!t) {
		t = m;
		w -= m;
	}
	return {(w / m) + 1,t};
}

void bfs() {
	memset(dis,0x3f,sizeof dis);
	dis[S.fi][S.se][0] = 0;
	queue<pii> q;
	q.push({get(S.fi,S.se),0});
	while (q.size()) {
		auto t = q.front();
		q.pop();
		
		int ux = zb(t.fi).fi, uy = zb(t.fi).se;
		int type = t.se;
		
		for (int i = 0; i < 4; i ++) {
			int vx = ux + dx[i], vy = uy + dy[i];
			if (vx > n || !vx || vy > m || !vy)
				continue;
			int vt = type;
			if (w[get(vx,vy)].size()) {
				for (auto qwq : w[get(vx,vy)]) {
					vt ^= (1 << qwq);
				}
			}
			if (dis[vx][vy][vt] <= inf / 2 || temp[type][vx][vy])
				continue;
			dis[vx][vy][vt] = dis[ux][uy][type] + 1;
			q.push({get(vx,vy),vt});
		}
	}
}

void init() {
	for (int i = 0; i < (1 << k); i ++) {
		memcpy(temp[i],flag,sizeof temp[i]);
		for (int j = 0; j < k; j ++) {
			if ((i >> j) & 1) {
				temp[i][h[j].x2][h[j].y2] ^= 1;
			}
		}
	}
}

int main(){
	n = fr(), m = fr();
	for (int i = 1; i <= n; i ++) {
		string s;
		cin >> s;
		for (int j = 0; j < n; j ++) {
			if (s[j] == 'S') S = {i,j + 1};
			else if (s[j] == 'T') T = {i,j + 1};
			else if (s[j] == '#') flag[i][j + 1] = true;
		}
	}
	k = fr();
	for (int i = 0; i < k; i ++) {
		int a,b,c,d;
		a = fr(), b = fr(), c = fr(), d = fr();
		h[i] = {a,b,c,d};
		w[get(a,b)].push_back(i);
	}
	init();
	bfs();
	for (int i = 0; i < (1 << k); i ++) {
		ans = min(ans,dis[T.fi][T.se][i]);
	}
	fw(ans);
	return 0;
}

1171 售货员的难题

 就是旅行商问题,一遍过捏。

点击查看代码
int n;
int dis[N][N];
int dp[M][N];

int main(){
	n = fr();
	for (int i = 0; i < n; i ++) {
		for (int j = 0; j < n; j ++) {
			dis[i][j] = fr();
		}
	}
	memset(dp,0x3f,sizeof dp);
	dp[1][0] = 0;
	for (int i = 2; i < (1 << n); i ++) {
		for (int j = 0; j < n; j ++) {
			if ((i >> j) & 1) {
				for (int k = 0; k < n; k ++) {
					if (i >> k & 1) {
						dp[i][k] = min(dp[i][k],dp[i ^ (1 << k)][j] + dis[j][k]);
					}
				}
			}
		}
	}
	int qwq = (1 << n) - 1;
	int ans = inf;
	for (int i = 1; i < n; i ++)
		ans = min(ans,dp[qwq][i] + dis[i][0]);
	fw(ans);
	return 0;
}

Day 33

4329 Bond

 一开始断网的时候 MLE 了,然后稍微修改了一下数组,就过了。下次会记得算空间的。

点击查看代码
int n,qwq;
double w[N][N];
double dp[M];

int main(){
	n = fr();
	for (int i = 0; i < n; i ++) {
		for (int j = 0; j < n; j ++) {
			cin >> w[i][j];
			w[i][j] /= 100.0;
		}
	}
	qwq = 1 << n;
	dp[0] = 1;
	for (int i = 0; i < qwq; i ++) {
		int cnt = 0;
		for (int j = 0; j < n; j ++) if (i & (1 << j)) cnt ++;
		for (int j = 0; j < n; j ++) {
			if (i & (1 << j))
				dp[i] = max(dp[i], dp[i ^ (1 << j)] * w[j][cnt - 1]);
		}
	}
	printf("%.6lf",100 * dp[qwq - 1]);
	return 0;
}

3052 Cows in a Skyscraper

 这个题目用了一些贪心,发现自己贪心的东西好像还不是很会,恼。

 贪心就是说如果说前面一个还塞得下的话,就尽量往前面一个塞,让这个空余的位置做到最小之后就不管了,直接开一个新的。

 然后转移的时候就直接按照普通的状压 dp 写就可以了。

点击查看代码
int n, qwq, W;
int w[N];
pii dp[M];

int main(){
	n = fr(), W = fr();
	for (int i = 0; i < n; i ++) w[i] = fr();
	qwq = 1 << n;
	for (int i = 0; i < qwq; i ++) {
		dp[i].fi = inf;
	}
	dp[0] = {1,W};
	for (int i = 0; i < qwq; i ++) {
		for (int j = 0; j < n; j ++) {
			if ((i >> j) & 1) continue;
			if (w[j] <= dp[i].se && dp[i | (1 << j)].fi >= dp[i].fi) {
				dp[i | (1 << j)] = {dp[i].fi,max(dp[i | (1 << j)].se,dp[i].se - w[j])};
			} else if (dp[i].se < w[j] && dp[i | (1 << j)].fi > dp[i].fi) {
				dp[i | (1 << j)] = {dp[i].fi + 1,max(dp[i | (1 << j)].se,W - w[j])};
			}
		}
	}
	fw(dp[qwq - 1].fi);
	return 0;
}

ACwing 1065 涂抹果酱

 是一个不是二进制的状压,就按照状压的写就可以了,就是不能直接右移和左移。一开始写的时候有一个循环的 nm 写反了,所以除了几个点(指 m n 相等的点)其他点都寄了,改了之后就过了。

点击查看代码
int n,m,k,qwq;
int flag;
int dp[2][mm];
int three[6] = {1,3,9,27,81,243};
vector<int> use,h[mm];

bool check(int i) {
	for (int j = 1; j < m; j ++) {
		if ((i / three[j - 1]) % 3 == (i / three[j]) % 3)
			return false;
	}
	return true;
}

void init() {
	for (int i = 0; i < qwq; i ++) {
		if (check(i)) use.push_back(i);
	}
	
	for (auto i : use) {
		for (auto j : use) {
			if (i == j) continue;
			bool flag = true;
			for (int k = 0; k < m; k ++) {
				if ((i / three[k]) % 3 == (j / three[k]) % 3) {
					flag = false;
					break;
				}
			}
			if (!flag) continue;
			h[i].push_back(j);
		}
	}
}

int main(){
	n = fr(), m = fr();
	k = fr();
	flag = 0;
	for (int i = 1; i <= m; i ++) {
		flag = fr() - 1 + flag * 3;
	}
	qwq = three[m];
	init();
	int u,v;
	for (int i = 1; i <= n; i ++) {
		u = i & 1, v = 1 - u;
		memset(dp[u],0,sizeof dp[u]);
		if (i == k) {
			if (i == 1) dp[u][flag] = 1;
			else 
			for (auto j : h[flag])
				dp[u][flag] = (dp[u][flag] + dp[v][j]) % mod;
			continue; 
		}
		for (int j : use) {
			for (auto k : h[j]) {
				dp[u][j] = (dp[u][j] + dp[v][k]) % mod;
			}
			if (i == 1) dp[u][j] = 1;
		}
	}
	lwl ans = 0;
	for (auto i : use) {
		ans = (ans + dp[u][i]) % mod;
	}
	fw(ans);
	return 0;
}

Day 34

5856 Game

 这个题目是先把每个数因式分解,然后记录一下每一个质数有的次数,然后用 flag 标记一下。

 然后在算答案之前,因为质数最多的次数是 20 ,这个时候就可以用状压 dp 来预处理出每一种次数对应的所需的最小的消除次数。

 然后因为他的要求是最后消到一样的数,所以一开始找到这些数的 gcd ,然后每次计算的时候,如果说当前遍历的这个质数是所有数的 gcd 的因数,那么这个数就不用全部消完,在计算答案的时候取一个 min 就可以咯。

点击查看代码
int n;
int w[N],h[N * 10];
int pre[N * 10];
int zs[20];
vector<int> qwqq;
int flag[N * 10][20];
int dp[1 << 20];

int gcd(int x,int y) {
	if (!y) return x;
	return gcd(y, x % y);
}

void init() {
	for (int i = 2; i <= 1e6; i ++) {
		if (!pre[i]) {
			pre[i] = i;
			qwqq.push_back(i);
		}
		for (auto j : qwqq) {
			if (j > 1e6 / i) break;
			pre[j * i] = j;
			if (i % j == 0) break;
		}
	}
	memset(dp,0x3f,sizeof dp);
	dp[0] = 0;
	int en = (1 << 20) - 1;
	for (int j = 0; j < 20; j ++) {
		dp[1 << j] = 1;
	}
	for (int t = 3; t <= en; t ++) {
		for (int k = 0; k < 20; k ++) {
			int t1 = (t & ((1 << k) - 1));
			dp[t] = min(dp[t], dp[(t >> (k + 1)) | t1] + 1);
		}
	}
}

signed main(){
	n = fr();
	init();
	int t = 0;
	int maxn = 0;
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		if (!t) t = w[i];
		else t = gcd(t,w[i]);
	}
	for (int i = 1; i <= n; i ++) {
		maxn = max(w[i],maxn);
		while (pre[w[i]] && w[i] != 1) {
			int t = pre[w[i]];
			int cnt = 0;
			while (w[i] % t == 0) {
				cnt ++;
				w[i] /= t;
			}
			flag[t][cnt] = true;
		}
	}
	while (pre[t] && t != 1) {
		int tt = pre[t];
		int cnt = 0;
		while (t % tt == 0) {
			cnt ++;
			t /= tt;
		}
		h[tt] = cnt;
	}
	int ans = 0;
	for (auto i : qwqq) {
		if (i > maxn) break;
		int en = 0;
		for (int j = 1; j <= 20; j ++) {
			if (flag[i][j]) {
				en |= (1 << (j - 1));
			}
		}
		if (!en) continue;
		int minn = dp[en];
		for (int j = 1; j <= h[i]; j ++)
			minn = min(minn,dp[en >> j]);
		ans += minn;
	}
	fw(ans);
	return 0;
}

3888 拯救莫莉丝

 这个题的循环有点多,然后要注意的一点是这一题给的范围是 mn50 ,并且要求 mn ,这样子的话 m 的最大值就只有 7 ,就可以状压了。

点击查看代码
int n,m;
int w[N][N];
int sum[N][K];
int dp[N][K][K],f[N][K][K];
int cnt[K];

int main(){
	n = fr(), m = fr();
	int qwq = (1 << m);
	for (int i = 1; i <= n; i ++) {
		for (int j = 0; j < m; j ++) {
			w[i][j] = fr();
		}
		for (int j = 0; j < qwq; j ++) {
			for (int t = 0; t < m; t ++) {
				if ((j >> t) & 1)
					sum[i][j] += w[i][t];
			}
		}
	}
	cnt[1] = 1;
	for (int i = 2; i < qwq; i ++) {
		cnt[i] = cnt[i >> 1] + (i & 1);
	}
	memset(dp,0x3f,sizeof dp);
	memset(f,0x3f,sizeof f);
	for (int i = 0; i < qwq; i ++) {
		dp[1][i][0] = sum[1][i];
		f[1][i][0] = cnt[i];
	}
	for (int i = 2; i <= n + 1; i ++) {
		for (int j = 0; j < qwq; j ++) {
			// 第 i 行
			for (int k = 0; k < qwq; k ++) {
				// 第 i - 1 行
				for (int t = 0; t < qwq; t ++) {
					// 第 i - 2 行
					// 保证第 i - 1 行是都有的
					if ((((j | k | t) | (k >> 1) | (k << 1)) & (qwq - 1)) == (qwq - 1)) {
						if (dp[i][j][k] > sum[i][j] + dp[i - 1][k][t]) {
							dp[i][j][k] = sum[i][j] + dp[i - 1][k][t];
							f[i][j][k] = f[i - 1][k][t] + cnt[j];
						} else if (dp[i][j][k] == sum[i][j] + dp[i - 1][k][t]) {
							f[i][j][k] = min(f[i][j][k],f[i - 1][k][t] + cnt[j]);
						}
					}
				}
			}
		}
	}
	pii ans = {inf,inf};
	for (int i = 0; i < qwq; i ++) {
		if (ans.fi > dp[n + 1][0][i]) {
			ans = {dp[n + 1][0][i],f[n + 1][0][i]};
		} else if (ans.fi == dp[n + 1][0][i]) {
			ans.se = min(ans.se,f[n + 1][0][i]);
		}
	}
	fw(ans.se);
	kg;
	fw(ans.fi);
	return 0;
}

Day 35

外太空旅行

 对于这个诈骗题我不好做评价,怎么会有题目的题解全部都是随机化的啊!怎么会有题目的标签有随机化的啊!

 当个笑话看算了。

点击查看代码
int n;
lwl qwq;
int w[N];
bool flag[N][N];
bool vis[N];
int ans = 1;

int main(){
	n = fr();
	int a,b;
	while (cin >> a >> b) {
		flag[a][b] = flag[b][a] = true;
	}
	int ans = 1;
	for (int i = 1; i <= n; i ++) w[i] = i;
	for (int i = 0; i <= 92; i ++) {
		random_shuffle(w + 1, w + n + 1);
		memset(vis,0,sizeof vis);
		int cnt = 0;
		for (int i = 1; i <= n; i ++) {
			if (!vis[w[i]]) {
				for (int j = 1; j <= n; j ++) {
					if (!flag[w[i]][w[j]]) vis[w[j]] = true;
				}
				cnt ++;
			}
		}
		ans = max(ans,cnt);
		if (ans == n) break;
	}
	fw(ans);
	return 0;
}

4802 短路最

 很简单的旅行商问题(),直接水过。

点击查看代码
int n, m;
int dis[N][N];
int  dp[N][M];

int main(){
	n = fr(), m = fr();
	memset(dis,-0x3f,sizeof dp);
	while (m -- ){
		int a = fr(), b = fr(), w = fr();
		dis[a][b] = w;
	}
	dp[0][1] = 0;
	int qwq = (1 << n);
	for (int i = 3; i < qwq; i ++) {
		for (int j = 1; j < n; j ++) {
			if (!((i >> j) & 1)) continue;
			for (int k = 0; k < n; k ++) {
				if (!((i >> k) & 1) || j == k) continue;
				dp[j][i] = max(dp[j][i],dp[k][i ^ (1 << j)] + dis[k][j]);
			}
		}
	}
	int ans = -inf;
	for (int i = (1 << (n - 1)); i < qwq; i ++) {
		ans = max(ans,dp[n - 1][i]);
	}
	fw(ans);
	return 0;
}

Day 36

6962 朋也与光玉

 一遍过捏。感觉挺水的()

点击查看代码
int n, m, k;
int w[N];
vector<node> e[14][N];
vector<int> h[N];
lwl dp[N][K];

int main(){
	memset(dp,0x3f,sizeof dp);
	n = fr(), m = fr(), k = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		dp[i][(1 << w[i])] = 0;
		h[w[i]].push_back(i);
	}
	while (m --) {
		int a = fr(), b = fr(), val = fr();
		if (w[a] == w[b]) continue;
		e[w[b]][a].push_back({b,val});
	}
	int qwq = (1 << k) - 1;
	for (int i = 3; i <= qwq; i ++) {
		for (int j = 0; j < k; j ++) {
			if (!(i & (1 << j))) continue;
			for (auto &u : h[j]) {
				for (int t = 0; t < k; t ++) {
					if (t == j || !((i >> t) & 1)) continue;
					for (auto &it : e[t][u]) {
						int v = it.v,w = it.w;
						dp[u][i] = min(dp[u][i],dp[v][i ^ (1 << j)] + w);
					}
				}
			}
		}
	}
	lwl ans = linf;
	for (int i = 1; i <= n; i ++)
		ans = min(ans,dp[i][qwq]);
	if (ans >= inf) wj;
	else fw(ans);
	return 0;
}

3070 Island Travels G

 这个题先找出所有连通块,然后求出每两个连通块之间的距离最小值,这个用 bfsspfa 就可以了(虽然我找连通块用的是 dfs )。最后再状压一下(就和旅行商问题一样的)

 在断网的时候写的是用 bfs 找最短路,所以寄了,改成 spfa 就过了。

点击查看代码
struct node{
	int x,y;
	int type;
};

int n,m;
int idx = 0;
int flag[N][N];
int w[N][N];
int dx[4] = {0,1,0,-1};
int dy[4] = {1,0,-1,0};
int dis[15][15];
int dp[N][K];
int dist[N][N];
bool vis[N][N];
vector<pii> h[N];

void dfs(int x,int y) {
	bool flg = false;
	w[x][y] = idx;
	for (int i = 0; i < 4; i ++) {
		int vx = x + dx[i],vy = y + dy[i];
		if (!vx || !vy || vx > n || vy > m) continue;
		if (flag[vx][vy] == 1) flg = true;
		if (flag[vx][vy] || ~w[vx][vy]) continue;
		w[vx][vy] = idx;
		dfs(vx,vy);
	}
	if (flg) h[idx].push_back({x,y});
}

void spfa(int u) {
	memset(dist,0x3f,sizeof dist);
	queue<pii> q;
	for (auto t : h[u]) {
		vis[t.fi][t.se] = 1;
		dist[t.fi][t.se] = 0;
		q.push(t);
	}
	while (q.size()) {
		auto t = q.front();
		q.pop();
		
		int x = t.fi,y = t.se;
		vis[x][y] = false;
		
		for (int i = 0; i < 4; i ++) {
			int vx = x + dx[i],vy = y + dy[i];
			if (!vx || !vy || vx > n || vy > m) continue;
			if (flag[vx][vy] == inf) continue;
			if (dist[vx][vy] > dist[x][y] + flag[vx][vy]) {
				dist[vx][vy] = dist[x][y] + flag[vx][vy];
				if (!flag[vx][vy])
					dis[u][w[vx][vy]] = min(dis[u][w[vx][vy]],dist[vx][vy]);
				if (!vis[vx][vy]) {
					vis[vx][vy] = true;
					q.push({vx,vy});
				}
				int t = dist[1][2];
			}

		}
	}
}

int main(){
	memset(w,-1,sizeof w);
	n = fr(), m = fr();
	for (int i = 1; i <= n; i ++) {
		string s;
		cin >> s;
		for (int j = 0; j < m; j ++) {
			if (s[j] == '.') flag[i][j + 1] = inf;
			else if (s[j] == 'S') flag[i][j + 1] = 1;
		}
	}
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) {
			if ((!flag[i][j]) && w[i][j] == -1) {
				dfs(i,j);
				idx ++;
			}
		}
	}
	memset(dp,0x3f,sizeof dp);
	memset(dis,0x3f,sizeof dis);
	for (int i = 0; i < idx; i ++) {
		spfa(i);
		dp[i][(1 << i)] = 0;
	}
	int qwq = (1 << idx) - 1;
	for (int i = 3; i <= qwq; i ++) {
		for (int j = 0; j < idx; j ++) {
			if (!((i >> j) & 1)) continue;
			for (int k = 0; k < idx; k ++) {
				if (!((i >> k) & 1) || k == j) continue;
				dp[j][i] = min(dp[j][i],dp[k][i ^ (1 << j)] + dis[k][j]);
			}
		}
	}
	int ans = inf;
	for (int i = 0; i < idx; i ++) {
		ans = min(ans,dp[i][qwq]);
	}
	fw(ans);
	return 0;
}

8280 Photoelectric Effect

 这个题是带状压的树形 dp

 在做的时候需要预处理一下如果两个状态合并起来可以变成什么颜色,后面转移的时候要用。

 然后这个地方在多测清空的时候,不能直接 memset ,不然会有几个点 TLE 掉。因为这个地方给的是所有 n ,所以对于每一个 n 单独预处理就可以了。

 然后就是在树形 dp 往下遍历的时候,这个地方 dp 的第三维状压表示的是在不包括当前点的当前点的子树中有颜色的状态。然后在到叶子节点的时候就把所有颜色的方案数都初始化为 1 就可以了。

 然后对于不是叶子节点的点,就先把他的第一个儿子给考虑了,因为如果只考虑一个子树的话子树的颜色是没有办法影响到当前点的状态的。所以就把所有颜色还有第一个子树的状态都遍历一下然后记录下来。而其它子树就要考虑前面的子树,因为只要有两个子树,那么这个节点的颜色就被决定了(这个就是前面预处理的东西),所以遍历一下当前子树的状态以及这个子树前面的所有子所对应的当前节点的状态,再决定这个节点是什么颜色的。

 然后因为这里之前处理的子树的状态是不包括当前子树的根节点的状态的,所以这里要把这个状态给 | 进去。并且因为这里状态转移的时候这个会重复更新,所以要先记录一下上一个子树的 dp 值,也就是代码中的 temp 数组,然后把 dp 清空进行下一轮的计算。

点击查看代码
int n,k;
int w[K][K];
int col[1 << K][1 << K];
vector<int> e[N];
lwl dp[N][K][1 << K];
lwl tmp[K][1 << K];

void dfs(int u) {
	if (!e[u].size()) {
		for (int i = 0; i < k; i ++)
			dp[u][i][0] = 1;
		return ;
	}
	for (auto v : e[u]) {
		dfs(v);
	}
	int v = e[u][0];
	for (int t = 0; t < k; t ++) {
		for (int i = 0; i < (1 << k); i ++) {
			for (int j = 0; j < k; j ++) {
				dp[u][t][i | (1 << j)] = (dp[u][t][i | (1 << j)] + dp[v][j][i]) % mod;
			}
		}
	}
	for (int i = 1; i < e[u].size(); i ++) {
		int v = e[u][i];
		for (int ii = 0; ii < (1 << k); ii ++) {
			for (int j = 0; j < k; j ++) {
				tmp[j][ii] = dp[u][j][ii];
				dp[u][j][ii] = 0;
			}
		}
		for (int j = 1; j < (1 << k); j ++) {
			for (int t = 0; t < (1 << k); t ++) {
				for (int _ = 0; _ < k; _ ++) {
					int uu = (1 << _) | t;
					if (col[j][uu] == -1) continue;
					dp[u][col[j][uu]][j | uu] = (dp[u][col[j][uu]][j | uu] + dp[v][_][t] * tmp[col[j][uu]][j] % mod) % mod;
				}
			}
		}
	}
}

void get(int x,int y) {
	col[x][y] = k;
	for (int i = 0; i < k; i ++) {
		if (!((x >> i) & 1)) continue;
		for (int j = 0; j < k; j ++) {
			if (!((y >> j) & 1)) continue;
			if (col[x][y] == k)
				col[x][y] = w[i][j];
			if (w[i][j] != col[x][y] || w[j][i] != col[x][y]){
				col[x][y] = -1;
				return ;
			}
		}
	}
}

int main(){
	int T = fr();
	while (T --) {
		memset(col,0,sizeof col);
		n = fr(), k = fr();
		for (int i = 0; i < k; i ++) {
			for (int j = 0; j < k; j ++) {
				w[i][j] = fr() - 1;
			}
		}
		for (int i = 1; i <= n; i ++) {
			for (int j = 0; j < k; j ++) {
				for (int qwq = 0; qwq < (1 << k); qwq ++) {
					dp[i][j][qwq] = 0;
				}
			}
		}
		for (int i = 1; i < (1 << k); i ++) {
			for (int j = 1; j < (1 << k); j ++) {
				get(i,j);
			}
		}
		for (int i = 1; i <= n; i ++) e[i].clear();
		for (int i = 2; i <= n; i ++) {
			e[fr()].push_back(i);
		}
		dfs(1);
		lwl ans = 0;
		for (int i = 0; i < k; i ++) {
			for (int j = 0; j < (1 << k); j ++) {
				ans = (ans + dp[1][i][j]) % mod;
			}
		}
		fw(ans);
		ch;
	}
	return 0;
}

Day 37

2167 Bill的挑战

 一遍过捏。状压 dp 。这个 dp 数组的定义是截止到当前第 i 位,前面位可以匹配上的状态 j

 遍历每一位,然后枚举一下这个位置是哪一个字母,然后遍历一遍每个字母,如果说当前位不匹配的话就标记一下,然后把前面的状态遍历一下再转移过来。

点击查看代码
int n, k;
string s[N];
lwl dp[55][1 << N];
// 第几位,只考虑前面的匹配
bool flag[26];
int cnt[1 << N];

void init() {
	for (int i = 1; i < (1 << N); i ++)
		cnt[i] = cnt[i >> 1] + (i & 1);
}

int main(){
	int T = fr();
	init();
	while (T --) {
		n = fr(), k = fr();
		int len = 0;
		for (int i = 0; i < n; i ++) {
			cin >> s[i];
			if (!len) len = s[i].length();
			bool flag = true;
			for (int j = 0; j < len; j ++) {
				if (s[i][j] != '?') {
					flag = false;
					break;
				}
			}
			if (flag) {
				i --, k --, n --;
			}
		}
		if (k > n || k < 0) {
			wj;
			continue;
		}
		int qwq = (1 << n) - 1;
		memset(dp,0,sizeof dp);
		dp[0][qwq] = 1;
		for (int i = 0; i < len; i ++) {
			int t = i + 1;
			memset(flag,0,sizeof flag);
			for (int kk = 0; kk < 26; kk ++) {
				int u = 0;
				for (int j = 0; j < n; j ++)
					if (s[j][i] - 'a' == kk || s[j][i] == '?') 
						u |= (1 << j);
				for (int o = 0; o <= qwq; o ++)
					dp[t][o & u] = (dp[t][o & u] + dp[t - 1][o]) % mod;
			}
		}
		lwl ans = 0;
		for (int i = 0; i <= qwq; i ++) {
			if (cnt[i] == k)
				ans = (ans + dp[len][i]) % mod;
		}
		fw(ans);
		ch;
	}
	return 0;
}

2157 学校食堂

 这个题目是状压 + 线性 dp。然后这个的状态定义是:遍历到第 i 个,上一个菜相对于 i 是第 j 个 后面七位的状态 k 前面的 i1 都已经被安排完的最小花费时间。

 然后转移的时候考虑两种情况,一个是从当前位可以转移到下一位,也就是说当前状态自己这个位置所对应的是已经吃过饭了的。

 还有一种情况就是说还是这一位,自己内部转移,也就是后面那个 else 里面的内容,最后统计答案的时候就是 min(dp[n+1][i][0]) 。这个 i 就是最后一个吃饭的人在哪里。

 然后因为这里遍历前一个吃饭的人是谁的时候,要遍历前面的人,也就是说这里的 j 是要从负数开始的,为了防止越界,所以把所有数组往后面平移就可以了。

点击查看代码
int n;
pii w[N];
int dp[N][20][K];
// 遍历到第几个 上一个菜是哪一个(相对位置) 前后的状态

int main(){
	int T = fr();
	while (T --) {
		n = fr();
		memset(dp,0x3f,sizeof dp);
		for (int i = 1; i <= n; i ++) {
			w[i].fi = fr();
			w[i].se = fr();
		}
		dp[1][7][0] = 0;
		int qwq = (1 << 8) - 1;
		for (int i = 1; i <= n; i ++) {
			for (int k = 0; k <= qwq; k ++) {
				for (int j = -8; j <= 7; j ++) {
					int u = j + 8;
					if (dp[i][u][k] < inf) {
						if (k & 1)
							dp[i + 1][u - 1][k >> 1] = min(dp[i + 1][u - 1][k >> 1],dp[i][u][k]);
						else {
							int limit = inf;
							for (int l = 0; l <= 7; l ++) {
								if (l + i > limit) break;
								if ((k >> l) & 1) continue;
								limit = min(limit,w[i + l].se + i + l);
								int ww = dp[i][u][k];
								if (i + j > 0) 
									ww += (w[i + j].fi ^ w[i + l].fi);
								dp[i][l + 8][k | (1 << l)] = min(dp[i][l + 8][k | (1 << l)],ww);
							}
						}
					}
				}
			}
		}
		int ans = inf;
		for (int i = 0; i <= 8; i ++) {
			ans = min(ans,dp[n + 1][i][0]);
		}
		fw(ans);
		ch;
	}
	return 0;
}

Day 38

ARC132C Almost Sorted

题目传送门

 一遍过捏,和学校食堂很像,还比学校食堂更简单些。

点击查看代码
int n,d;
int w[N];
lwl dp[N][(1 << 11) + 5];
bool flag[N];

int main(){
	n = fr(), d = fr();
	bool st = false;
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		flag[w[i]] = true;
		if (w[i] - i > d) {
			st = true;
			break;
		}
	}
	if (st) {
		wj;
		return 0;
	}
	int qwq = (1 << (d * 2 + 1)) - 1;
	int h = d * 2 + 1;
	dp[0][0] = 1;
	int awa = (1 << (d * 2));
	for (int i = 1; i <= n; i ++) {
		for (int k = 0; k <= qwq; k ++) {
		// 上一个数的状态
			for (int j = 1; j < h; j ++) {
				if ((k >> j) & 1) continue;
				int t = i + j - d - 1;
				if ((~w[i]) && t != w[i]) continue;
				if (t <= 0 || (flag[t] && w[i] != t)) continue;
				int u = (k >> 1) | (1 << (j - 1));
				dp[i][u] = (dp[i][u] + dp[i - 1][k]) % mod;
			}
			if (w[i] == i + d || w[i] == -1)
				dp[i][(k >> 1) + awa] = (dp[i][(k >> 1) + awa] + dp[i - 1][k]) % mod;
		}
	}
	lwl ans = dp[n][(1 << (d + 1)) - 1];
	fw(ans);
	return 0;
}

Day 39

6289 Vijestica

题目传送门

 状压 dp 。因为可以随意组织,所以说给的字母顺序不重要,只要考虑每个字符串中每个字母有多少个就可以了。

 然后我们再考虑合并,先看合并两个字符串的时候,定义一个数 sum ,这个 sum 就是每个字母在两个字符串中的最小的和,也就是代码中的 awa。然后这两个字符串合并的最小子节点就是 :

s1.length()+s2.length()sum

 然后再进一步,就考虑两个以上的字符串合并的时候,其实就和两个合并差不多的,我们可以先合并其中的一部分字符串和另一部分字符串,然后再把这两个合并的字符串合并在一起就可以了,其实就和两个合并在一起是一样的,也是找一下 sum 就可以了,不同的就是这里要枚举子集,并且在取 min 的时候是所有选取的字符串的字母数取最小值。

点击查看代码
int n;
lwl w[N][N];
int len[N];
lwl dp[K];

int main(){
	int n = fr();
	for (int i = 0; i < n; i ++) {
		string s;
		cin >> s;
		int lth = s.length();
		for (int j = 0; j < lth; j ++)
			w[i][s[j] - 'a'] ++;
		len[i] = lth;
	}
	int qwq = (1 << n) - 1;
	memset(dp,0x3f,sizeof dp);
	for (int i = 0; i < n; i ++)
		dp[1 << i] = len[i];
	for (int i = 0; i <= qwq; i ++) {
		if ((i & -i) == i) continue;
		lwl awa = 0;
		for (int t = 0; t < 26; t ++) {
			lwl minn = linf;
			for (int j = 0; j < n; j ++) {
				if ((i >> j) & 1)
					minn = min(minn,w[j][t]);
			}
			awa += minn;
		}
		for (int j = i & (i - 1); j; j = i & (j - 1)) {
			dp[i] = min(dp[i],dp[j] + dp[i ^ j] - awa);
		}
	}
	fw(dp[qwq] + 1);
	return 0;
}

9029 Cokolade

题目传送门

 这个朴素的 O(n2) 很好想。

 正解的话,先排一个序,很明显这个选取买哪些的时候,肯定是前面选一段后面选一段,所以需要考虑的就是该怎么选。

 首先要找的肯定就是这个价格的分界线在哪里,这个地方用一个 lower_bound 或者手写二分都可以,我不太喜欢用 lower_bound ,所以写的是二分。然后找到这个点之后,就找一下从哪一个点断掉。

 找到之后就二分一下该怎么分,这里二分的是当当前的点(前面一半)是所有选取的数里面的最大值的时候,最多可以选多少个巧克力,然后当他 m 的时候,就说明这个作为最大值是可以的。而且因为一段段的用,所以预处理一下前缀和捏。

点击查看代码
int n,T;
int w[N];
lwl k,m;
lwl sum[N];

lwl get(int l,int r) {
	return sum[r] - sum[l - 1];
}

int check(lwl x) {
	int l = 0, r = n;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (w[mid] < x) l = mid + 1;
		else r = mid - 1;
	}
	return n - l + 1;
}

signed main(){
	n = fr(), T = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
	}
	sort(w + 1,w + 1 + n);
	for (int i = 1; i <= n; i ++) 
		sum[i] = sum[i - 1] + w[i];
	while (T --) {
		k = fr(), m = fr();
		int l = 0, r = n;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (w[mid] <= k) l = mid + 1;
			else r = mid - 1; 
		}
		lwl pos = r;
		l = 0, r = min(m,pos);
		int res = l;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (check(2 * k - w[mid]) + mid <= m) {
				l = mid + 1;
				res = mid;
			}
			else r = mid - 1;
		}
		lwl ans = sum[res] + 2 * k * (m - res) - get(n - m + res + 1,n);
		fw(ans);
		ch;
	}
	return 0;
}

Day 40

2607 骑士

题目传送门

 这个是基环树加上 dp ,因为这个是森林,所以在跑的时候要注意要把每一个点都跑一遍。然后再按照没有上司的舞会跑一遍就可以了。

 然后因为这里要标记边,所以存边的 vector 里面存了一个 idx ,方便后面标记边。

点击查看代码
struct node {
	int v;
	int idx;
};

int n;
lwl dp[N][2];
int w[N];
vector<node> e[N];
int s1,s2;
int t;
bool flag[N];

void dfs(int u,int fa) {
	flag[u] = true;
	for (auto it : e[u]) {
		int v = it.v;
		if (v == fa) continue;
		if (flag[v]) {
			s1 = u,s2 = v;
			t = it.idx;
			continue;
		}
		dfs(v,u);
	}
}

void dfs1(int u,int fa) {
	dp[u][0] = 0;
	dp[u][1] = w[u];
	for (auto it : e[u]) {
		if (it.idx == t) continue;
		int v = it.v;
		if (v == fa) continue;
		dfs1(v,u);
		dp[u][1] = dp[u][1] + dp[v][0];
		dp[u][0] = dp[u][0] + max(dp[v][0],dp[v][1]);
	}
}

int main(){
	n = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		int a = fr();
		e[a].push_back({i,i});
		e[i].push_back({a,i});
	}
	lwl ans = 0;
	for (int i = 1; i <= n; i ++) {
		if (!flag[i]) {
			dfs(i,0);
			lwl res;
			dfs1(s1,0);
			res = dp[s1][0];
			dfs1(s2,0);
			res = max(res,dp[s2][0]);
			ans += res;
		}
	}
	fw(ans);
	ch;
	return 0;
}

5049 旅行 加强版

题目传送门

 这个题目的普通版就是找出环,然后把环上每一条边都断一遍然后跑一遍 dfs 就可以。

 加强之后原来朴素的 O(n2) 肯定是过不了的,所以就要想一下别的。然后这里因为有环,所以和树的不一样就是这里其实是可以反悔一次的,所以说可以考虑一下贪心(应该算是贪心吧)。

 这个贪心的核心就是:当你的前一个还有儿子没走的祖先的最小的儿子比你自己的儿子(在环上的并且其它儿子都访问完了或者没有没有其它儿子)小的时候就反悔。然后记录一下有没有反悔过就可以啦!

注意:这个地方因为在里面要把没有用过的点从小到大排序,如果先在外面排序然后里面用一个queue的话是会爆空间的,在里面用priority_queue就不会爆(虽然不知道为什么,但是实测是这样的)

 然后这个找环的改了好几版,这个是标记点的,然后在这个代码里面的是标记点的写法,还有一种标记边的时候的写法也放在这里了:

这里是标记边的写法
bool find_ring(int u,int fa) {
	flag[u] = true;
	for (auto it : e[u]) {
		int v = it.v;
		if (v == fa) continue;
		if (flag[v]) {
			s1 = u, s2 = v;
			ring.push_back(it.idx);
			return true;
		}
		if (find_ring(v,u)) {
			ring.push_back(it.idx);
			if (u != s2) return true;
			return false;
		}
	}
	return false;
}
题目的完整(?)代码
int n, m;
int s1,s2;
bool type = false;
vector<int> e[N];
bool ring[N], flag[N];
int ans[N];
bool vis = false;

bool find_ring(int u,int fa) {
	flag[u] = true;
	for (auto v : e[u]) {
		if (v == fa) continue;
		if (flag[v]) {
			s1 = u, s2 = v;
			ring[v] = ring[u] = true;
			return true;
		}
		if (find_ring(v,u)) {
			bool st = false;
			if (!(ring[u] && ring[v])) {
				st = true;
			}
			ring[v] = ring[u] = true;
			return st;
		}
	}
	return false;
}

void solve_tree(int u) {
	flag[u] = true;
	fw(u),kg;
	for (auto v : e[u]) {
		if (!flag[v])
			solve_tree(v);
	}
}

void solve_ring(int u,int fa,int back,int &cnt) {
	// 反悔
	if (flag[u]) return ;
	flag[u] = true;
	ans[++ cnt] = u;
	priority_queue<int,vector<int>,greater<int> > q;
	for (auto v : e[u]) {
		if (flag[v] || v == fa) continue;
		q.push(v);
	}
	while (q.size()) {
		int v = q.top();
		q.pop();
		
		if ((!vis) && ring[v] && v > back && q.empty()) {
			vis = true;
			return ;
		}
		
		int ne = back;
		if (q.size() && ring[u]) ne = q.top();
		solve_ring(v,u,ne,cnt);
	}
}

int main(){
	n = fr(), m = fr();
	if (m == n) type = true;
	while (m --) {
		int a = fr(), b = fr();
		e[a].push_back(b);
		e[b].push_back(a);
	}
	if (!type) {
		for (int i = 1; i <= n; i ++) 
			sort(e[i].begin(), e[i].end());
		solve_tree(1);
		return 0;
	}
	find_ring(1,0);
	int cnt = 0;
	memset(flag,0,sizeof flag);
	solve_ring(1,0,inf,cnt);
	for (int i = 1; i <= n; i ++) {
		fw(ans[i]);
		kg;
	}
	return 0;
}

Day 41

P5672 Crystal Balls

题目传送门

 应该算模拟吧,但是因为这里的模数很大,所以要用龟速乘。一开始不知道为什么可能常数有点大,所以 TLE了几个点,然后不知道改了什么就过了()

点击查看代码
lwl n, mod;
lwl w[N];
bool col[N];

void gsc(lwl &a,lwl b) {
	lwl ans = 0;
	while (b) {
		if (b & 1) ans = (ans + a) % mod;
		a = (a + a) % mod;
		b >>= 1;
	}
	a = ans;
}

int main(){
	n = fr(), mod = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr() % mod;
	}
	string s;
	cin >> s;
	for (int i = 0; i < n; i ++) {
		if (s[i] == 'P') col[i + 1] = 1;
	}
	int l = 1, r = n;
	bool type = true; // 现在是否从后往前
	bool purple = 0;
	lwl ans = 0;
	int cnt = 0;
	while (l < r) {
		int cv,cu;
		cnt ++;
		if (type) {
			cu = col[l] ^ purple;
			ans = (ans + w[l]) % mod;
			l ++;
			cv = col[l] ^ purple;
			if (cu == cv) {
				gsc(w[l],w[l - 1]);
			} else if (cu && cv == 0 && (cnt & 1)) {
				purple ^= 1;
			} else {
				type ^= 1;
			}
		} else {
			cu = col[r] ^ purple;
			ans = (ans + w[r]) % mod;
			r --;
			cv = col[r] ^ purple;
			if (cu == cv) {
				gsc(w[r],w[r + 1]);
			} else if (cu && cv == 0 && (cnt & 1)) {
				purple ^= 1;
			} else {
				type ^= 1;
			}
		}
	}
	ans = (ans + w[l]) % mod;
	fw(ans);
	return 0;
}

Day 42

P6381 Odyssey

题目传送门

 这个题目很明显是要用拓扑排序,但是我一开始断网的时候想的是标记每一条边,这就导致超时了,但是也有 65pts

代码
struct node {
	int v,w,l;
	int idx;
};

int n,m,k;
int d[N],dd[N];
vector<node> e[N];
unordered_map<lwl,bool> h;
pii w[N];
lwl dis[N];

void init() {
	h[0] = 1;
	for (int i = 1; i <= 1e5; i ++) {
		lwl t = 1;
		for (int j = 1; j <= k; j ++) {
			t = t * i;
			if (t > 1e10) break;
		}
		if (t <= 1e10) h[t] = true;
		else break;
	}
}

int main() {
	n = fr(), m = fr(), k = fr();
	if (k != 1) init();
	while (m --) {
		int a = fr(), b = fr(), ww = fr(), l = fr();
		e[a].push_back({b,ww,l,m});
		w[m] = {b,ww};
		d[b] ++;
	}
	int hh = 0, tt = 0;
	for (int i = 1; i <= n; i ++) {
		if (!d[i]) {
			for (auto &it : e[i]) {
				q.push(it.idx);
				dis[it.idx] = it.l;
			}
		}
		for (auto &it : e[i]) {
			dd[it.idx] = d[i];
		}
	}
	lwl ans = 0;
	while (q.size()) {
		int idx = q.front();
		q.pop();
		
		pii t = w[idx];
		lwl dist = dis[idx];
		
		ans = max(ans,dist);
		for (auto &it : e[t.fi]) {
			dd[it.idx] --;
			if (k == 1 || h[it.w * t.se]) {
				dis[it.idx] = max(dis[it.idx], dist + it.l);
			} else {
				dis[it.idx] = max(dis[it.idx],(lwl)it.l);
			}
			if (!dd[it.idx])
				q.push(it.idx);
		}
	}
	fw(ans);
	return 0;
}

 后面开了网之后,经过 xyzfrozen 的指点以及代码(?),搞懂了这个题。因为他要求最后求出来两条边是一个 xk ,所以可以先把每一个边进行质因数分解,然后就可以把这个边锁对应的那一类的边给求出来,也就是说这个边对应的类是一定的,所以就不用记录边了,按照点来拓扑然后做就可以咯。

 但是这个是按照点来拓扑,但是记录 dis 的时候还是要用边的类别和点放在一起记录的,然后要输出结果的话就把所有求的过程中的 dis 求一下就可以啦

点击查看代码
struct node {
	int v,w,l;
};

int n,m,k;
int d[N];
vector<node> e[N];
vector<int> zs;
int match[N];
map<pii,lwl> dis;
int pre[N];

void init() {
	for (int i = 2; i <= 1e5; i ++) {
		if (!pre[i]) {
			pre[i] = i;
			zs.push_back(i);
		}
		for (auto j : zs) {
			if (j > 1e5 / i) break;
			pre[i * j] = j;
			if (i % j == 0) break;
		}
	}
}

int get(int t) {
	unordered_map<int,int> qwq;
	while (t > 1) {
		qwq[pre[t]] ++;
		t /= pre[t];
	}
	int a = 1, b = 1;
	for (auto it : qwq) {
		a = a * pow(it.fi,(it.se % k));
		b = b * pow(it.fi,(k - it.se % k) % k);
	}
	match[a] = b;
	return a;
}

int main() {
	n = fr(), m = fr(), k = fr();
	init();
	while (m --) {
		int a = fr(), b = fr(), ww = fr(), l = fr();
		ww = get(ww);
		e[a].push_back({b,ww,l});
		d[b] ++;
	}
	queue<int> q;
	for (int i = 1; i <= n; i ++) {
		if (!d[i]) {
			q.push(i);
		}
	}
	lwl ans = 0;
	while (q.size()) {
		auto u = q.front();
		q.pop();
		
		for (auto it : e[u]) {
			d[it.v] --;
			dis[{it.v,it.w}] = max(dis[{it.v,it.w}],dis[{u,match[it.w]}] + it.l);
			ans = max(ans,dis[{it.v,it.w}]);
			if (!d[it.v]) q.push(it.v);
		}
	}
	fw(ans);
	return 0;
}

P4999 烦人的数学作业

题目传送门

 这个题目就是数位 dp ,虽然感觉用数学方法也可以搞,但是数位 dp ,写起来很方便,所以就不管他了。

 一遍过捏。

点击查看代码
void init() {
	ten[0] = 1;
	for (int i = 1; i <= 19; i ++) {
		w[i] = (w[i - 1] * 10 + 45 * ten[i - 1]) % mod;
		ten[i] = 10 * ten[i - 1] % mod;
	}
}

pii dfs(int u,bool limit) {
	if (u == 0) return{1,0};
	if (!limit) return {ten[u],w[u]};
	lwl ans = 0;
	int cnt = 0;
	int up = 9;
	if (limit) up = a[u];
	for (int i = 0; i <= up; i ++) {
		pii t = dfs(u - 1,up == i && limit);
		ans += i * t.fi + t.se;
		cnt += t.fi;
		ans %= mod;
		cnt %= mod;
	}
	return {cnt,ans};
}

lwl solve(lwl t) {
	if (!t) return 0;
	a.clear();
	a.push_back(0);
	while (t) {
		a.push_back(t % 10);
		t /= 10;
	}
	int n = a.size() - 1;
	return dfs(n,1).se;
}

int main() {
	int T = fr();
	init();
	while (T --) {
		lwl l = fr(),r = fr();
		lwl ans = solve(r) - solve(l - 1);
		fw((ans + mod + mod) % mod);
		ch;
	}
	return 0;
}

P5585 Doing Homework

题目传送门

 这个题其实感觉还是比较简单的,但是由于我一开始没有看见空间限制,所以 MLE 了,稍微压了一下空间之后就过了。

 下次一定好好看空间限制。

 大概做法就是,先按照 t 的值排一个序,然后按照背包来做,因为背包求的方法,所以可以知道这里求的时候 dp[i][j] 结束的时候就是只用了前 i 种物品的情况,所以就可以把每一个 dp[i][w] 记录下来,后面再去求。

点击查看代码
struct node {
	int x,w,t;
	bool operator < (const node tt) {
		return t > tt.t;
	}
};

int main() {
	x = fr(), W = fr();
	n = fr();
	for (int i = 1; i <= n; i ++) {
		w[i].x = fr(),w[i].w = fr(),w[i].t = fr();
	}
	sort(w + 1, w + 1 + n);
	memset(t,0x3f,sizeof t);
	memset(dp,0x3f,sizeof dp);
	dp[0] = 0;
	for (int i = 1; i <= n; i ++) {
		for (int j = w[i].w; j <= 2 * W; j ++) {
			dp[j] = min(dp[j],dp[j - w[i].w] + w[i].x);
		}
		for (int j = W; j <= 2 * W; j ++) {
			t[i] = min(t[i],dp[j]);
		}
	}
	for (int i = 1; i <= n; i ++) {
	}
	int u = n;
	int ans = 0;
	int ne = 0;
	while (x > 0 && u >= 1) {
		lwl tt = min(x / t[u],(lwl)w[u].t - ne);
		x -= t[u] * tt;
		ans += tt;
		if (tt != w[u].t - ne) break;
		ne = w[u].t;
		u --;
	}
	fw(ans),kg;
	fw(x);
	return 0;
}

P7291 人赢 加强版

题目传送门

 贪心, O(n) ,因为这一题的数据范围有点大,所以如果排一个序的话就会寄掉,所以普通的排序之后再算的话肯定是会寄掉的,所以考虑只遍历一遍的情况。

 贪心的话考虑从前往后遍历,在每一次遍历到一个点的时候,就先取一下 xy 取同一个值的情况,然后再用一个东西来记录一下当前遍到的最大值的下标,后面算的时候就直接用 jixy 来算就可以啦。

 证明的话就稍微感性理解一下算了,贪心哪需要证明啊(不是)。

点击查看代码
int n;
int w[N];

int main() {
	n = fr();
	lwl ans = 0;
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
		ans = max(ans,(lwl)w[i] * i);
	}
	int j = n;
	for (int i = n - 1; i; i --) {
		ans = max(ans,(lwl)min(w[i],w[j]) * (i + j));
		if (w[j] < w[i]) j = i;
	}
	fw(ans);
	return 0;
}

Day 43

7355 抽奖

题目传送门

 这个题目其实就是一个推结论的题目,但是有点前置知识:

 等比序列的求和公式:a1×(xn1)n1

 这里的 a1 指的是首项,n 是等比序列的项数,x 是等比的比

注:这个公式不能处理等比是 1 的情况

 有了这个公式之后,就可以推式子了。推式子的过程就省去了,这里就记录一下大概思路:考虑将多少兑换卷抽成体验卡,然后再考虑要将哪一个换成永久道具,然后就可以推出下面这个式子:

(n+1)(1+n+n2++nm)n(1+(n1)2(n1)m)

 然后按照这个式子直接计算就可以了,公式里面的除法要转化成逆元。

点击查看代码
lwl ksm(lwl a,lwl k) {
	lwl ans = 1;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

lwl ny(lwl a) {
	return ksm(a,mod - 2);
}

lwl sum(lwl x,lwl y) {
	if (!x) return 1;
	if (x == 1) return y;
	return 1ll * (ksm(x,y) - 1) % mod * ny(x - 1) % mod;
}

int main() {
	int T = fr();
	while (T --) {
		n = fr(), m = fr();
		lwl ans = (n + 1) * sum(n,m + 1) % mod;
		ans = (ans - n * sum(n - 1,m + 1) % mod) % mod;
		fw((ans + mod) % mod);
		ch;
	}
	return 0;
}

P8251 丹钓战

题目传送门

 倍增 + 模拟

 模拟之后可以知道,在 [L,R] ,会先把 PL 入栈,对于下一个成功的二元组,它会把栈顶元素直到 PL 都弹出,再把自己压进去。

 这样就可以发现,每一个二元组,要么被一个成功的二元组弹出去,要么留到末尾。因此就可以记录一下每个二元组会被哪一个二元组弹出去,之后再倍增做就可以了。

点击查看代码
int n;
pii w[N];
int ne[N][25];

int main(){
	n = fr();
	int T = fr();
	for (int i = 1; i <= n; i ++) w[i].fi = fr();
	for (int i = 1; i <= n; i ++) w[i].se = fr();
	stack<int> s;
	for (int i = 1; i <= n; i ++) {
		while (s.size() && (w[s.top()].fi == w[i].fi || w[s.top()].se <= w[i].se)) {
			ne[s.top()][0] = i;
			s.pop();
		}
		s.push(i);
	}
	int maxn = log2(n) + 1;
	for (int i = 1; i <= maxn; i ++) {
		for (int j = 1; j <= n; j ++) {
			ne[j][i] = ne[ne[j][i - 1]][i - 1];
		}
	}
	while (T --) {
		int l = fr(), r = fr();
		int ans = 1;
		for (int i = maxn; i >= 0; i --) {
			if (ne[l][i] <= r && ne[l][i]) {
				l = ne[l][i];
				ans += (1 << i);
			}
		}
		fw(ans);
		ch;
	}
	return 0;
}

P9437 一棵树

题目传送门

 换根 dp

 记录一下次数和总和,然后还要对于每一个数来说,求一下如果要和其它的点拼到一起的话,要乘上多少,这个其实也就是 10 的多少次方,这个位数也不是很多,可以直接在代码定义数组的时候写。

 然后开 dp 数组开三维,有一维记录了点要算的次数,还有一维记录了一开始以 1 为根的时候的答案,还有一维就是换根的时候用的了。

点击查看代码
int n, root;
int siz[N], fa[N];
pii w[N];
lwl dp[N][3];// 算的次数
vector<int> e[N];
lwl ten[11] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000};
int d[N];

lwl get(lwl a) {
	if (!a) return 10;
	int cnt = 0;
	while (a) {
		cnt ++;
		a /= 10;
	}
	return ten[cnt];
}

void dfs(int u,int father) {
	siz[u] = 1, dp[u][0] = 1;
	fa[u] = father;
	for (auto v : e[u]) {
		if (v == father) continue;
		dfs(v,u);
		siz[u] += siz[v];
		dp[u][0] = dp[u][0] + w[v].se * dp[v][0] % mod;
		// 以当前点为终点
		dp[u][0] %= mod;
	}
	dp[u][1] = dp[u][0];
}

void get(int u,int father) {
	for (auto v : e[u]) {
		if (v == father) continue;
		dp[u][2] += siz[v] * (dp[u][1] - dp[v][0] * w[v].se % mod) % mod;
		dp[u][2] %= mod;
		dp[v][1] = (dp[v][1] + (dp[u][1] - dp[v][0] * w[v].se % mod) * w[u].se % mod) % mod;
	}
	dp[u][2] += (n - siz[u]) * dp[u][0] % mod;
	dp[u][2] %= mod;
	for (auto v : e[u]) {
		if (v == father) continue;
		get(v,u);
	}
}

signed main() {
	n = fr();
	ten[10] %= mod;
	for (int i = 1; i <= n; i ++) {
		lwl a = fr();
		w[i] = {a,get(a)};
	}
	for (int i = 1; i < n; i ++) {
		int t = fr();
		e[t].push_back(i + 1);
		e[i + 1].push_back(t);
		d[t] ++, d[i + 1] ++;
	}
	root = 1;
	dfs(root,0);
	get(root,0);
	lwl ans = 0;
	for (int i = 1; i <= n; i ++) {
		ans = (ans + w[i].fi * (dp[i][1] + dp[i][2])) % mod;
	}
	fw((ans + mod) % mod);
	return 0;
}

Keep Connect

题目传送门

 因为这个地方只有两行,而且 N 的范围也不大,所以直接线性 dp 就可以搞了。

 状态定义的话就是 dp[i][j][0/1] 代表的就是考了前 i 列,删去了 j 条边是否连通的方案,然后根据六个点(多一点也可以,六个点就够了)的情况模拟一下,可以得出转移方程式。具体方程式都在代码里面,就不细写了。

点击查看代码
int n,mod;
lwl dp[N][N][2];
// 前i列 删去j条边 是否连通

int main(){
	n = fr(), mod = fr();
	dp[1][0][1] = dp[1][1][0] = true;
	for (int i = 1; i <= n; i ++) {
		for (int j = 0; j < n; j ++) {
			dp[i + 1][j][1] += dp[i][j][1];
			dp[i + 1][j][1] %= mod;
			
			dp[i + 1][j][1] += dp[i][j][0];
			dp[i + 1][j][1] %= mod;
				
			dp[i + 1][j + 1][0] += dp[i][j][0];
			dp[i + 1][j + 1][0] %= mod;
			
			dp[i + 1][j + 2][0] += 2 * dp[i][j][1];
			dp[i + 1][j + 2][0] %= mod;
			
			dp[i + 1][j + 1][1] += 3 * dp[i][j][1];
			dp[i + 1][j + 1][1] %= mod;
		}
	}
	for (int i = 1; i < n; i ++) {
		fw(dp[n][i][1]);
		kg;
	}
	return 0;
}

Day 44

P3871 中位数

题目传送门

 一遍过捏

 对顶堆模拟,保证小根堆里面的元素数量大于等于二分之一的总元素数量

点击查看代码
int main() {
	int n = fr();
	priority_queue<int,vector<int>,greater<int> > da;
	priority_queue<int> xiao;
	int siz = n;
	for (int i = 1; i <= n; i ++) {
		xiao.push(fr());
	}
	for (int i = 1; i <= (siz >> 1); i ++) {
		da.push(xiao.top());
		xiao.pop();
	}
	int m = fr();
	while (m --) {
		string op;
		cin >> op;
		if (op[0] == 'a') {
			siz ++;
			int a = fr();
			if (a <= xiao.top()) xiao.push(a);
			else da.push(a);
			if (xiao.size() > da.size() + 1) {
				da.push(xiao.top());
				xiao.pop();
			}
			if (xiao.size() < da.size()) {
				xiao.push(da.top());
				da.pop();
			}
		} else {
			fw(xiao.top());
			ch;
		}
	}
	return 0;
}

P4071 排列计数

题目传送门

 一遍过捏

 错排吧应该是,虽然当时做的时候没有想到错排的结论,于是就打表看了一下错排的数量,然后找规律推出来一个这样的式子

w[i]={w[i1]i1x%2==1w[i1]i+1x%2==0

 然后就是选出 nm 个位置匹配上,剩下的就用上面的公式进行计算。这里组合数预处理一下就可以了。一开始预处理组合数逆元的时候乘的时候 i+1 写成 i 了。

点击查看代码
lwl ny(lwl a) {
	lwl ans = 1, k = mod - 2;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

void init() {
	w[0] = 1;
	for (int i = 1; i <= N - 5; i ++) {
		if (i & 1) w[i] = (w[i - 1] * i - 1) % mod;
		else w[i] = (w[i - 1] * i + 1) % mod;
	}
	f[0] = nf[0] = 1;
	for (int i = 1; i <= N - 5; i ++) {
		f[i] = f[i - 1] * i % mod;
	}
	nf[N - 5] = ny(f[N - 5]);
	for (int i = N - 6; i >= 0; i --) {
		nf[i] = nf[i + 1] * (i + 1) % mod;
	}
}

lwl C(int a,int b) {
	if (a == b || !b) return 1;
	return f[a] * nf[a - b] % mod * nf[b] % mod;
}

int main() {
	init();
	int T = fr();
	while (T --) {
		n = fr(), m = fr();
		fw(C(n,m) * w[n - m] % mod);
		ch;
	}
	return 0;
}

P4588 数学计算

题目传送门

 一遍过捏。

 线段树。因为这一题的模数不一定是质数,所以说不能用逆元直接算,但是可以用线段树维护,在他进行某个操作的时候就把那个位置的值改为要乘的值,删除某个值的时候就是把那个位置的值改为 1 。输出的话就是输出所有操作的和,也就是 tr[1].mul

点击查看代码
int Q,mod;

void push_up(int idx) {
	tr[idx].mul = tr[il].mul * tr[ir].mul % mod;
}

void build(int l,int r,int idx) {
	L = l,R = r;
	tr[idx].mul = 1;
	if (L == R) return ;
	int mid = (l + r) >> 1;
	build(l,mid,il);
	build(mid + 1,r,ir);
}

void modify(int idx,int pos,int k) {
	if (L == R) {
		tr[idx].mul = k;
		return ;
	}
	int mid = (L + R) >> 1;
	if (mid >= pos) modify(il,pos,k);
	else modify(ir,pos,k);
	push_up(idx);
}

int main() {
	int T = fr();
	while (T --) {
		Q = fr(), mod = fr();
		int cnt = 0;
		build(1,Q,1);
		while (Q --) {
			cnt ++;
			int op = fr();
			if (op & 1) {
				lwl k = fr();
				modify(1,cnt,k);
			} else {
				lwl pos = fr();
				modify(1,pos,1);
			}
			fw(tr[1].mul);
			ch;
		}
	}
	return 0;
}

P8955 Card Tricks

题目传送门

 一遍过捏。

 这个题目是比赛里面的一个题目,当时还参加了这个比赛,但是当时这一题好像打的是暴力。后面补题的时候也补了,所以这次会做。我竟然还记得这一题,我哭死。

 像是扫描线的写法,好像叫线段树二分。像差分一样存储一下操作,然后遍历每一个数,在遍历这个数的时候,先将之前存储的在这个数这里的操作操作一下,然后线段树二分出最小的操作使得这个时候的值大于 p

点击查看代码
void push_up(int idx) {
	tr[idx].w = tr[il].w | tr[ir].w;
}

void build(int l,int r,int idx) {
	L = l, R = r;
	if (l == r) return ;
	int mid = (l + r) >> 1;
	build(l,mid,il);
	build(mid + 1,r,ir);
}

void modify(int idx,int pos,int val) {
	if (L == R) {
		tr[idx].w = val;
		return ;
	}
	int mid = (L + R) >> 1;
	if (mid >= pos) modify(il,pos,val);
	else modify(ir,pos,val);
	push_up(idx);
}

int query(int idx,int val) {
	if ((tr[idx].w | val) <= p) return -1;
	if (L == R) {
		if ((tr[idx].w | val) > p) return L;
		return -1;
	}
	if ((tr[il].w | val) > p) return query(il,val);
	else return query(ir,val | tr[il].w);
}

int main() {
	freopen("qwq.in","r",stdin);
	n = fr(), Q = fr(),p = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
	}
	for (int i = 1; i <= Q; i ++) {
		int l = fr(), r = fr(), v = fr();
		q[l].push_back({i,v});
		q[r + 1].push_back({i,0});
	}
	build(1,Q,1);
	for (int i = 1; i <= n; i ++) {
		for (auto &it : q[i]) {
			modify(1,it.fi,it.se);
		}
		fw(query(1,w[i]));
		kg;
	}
	return 0;
}

Day 45

P7795 PROSJEK

题目传送门

 一开始写的时候以为这一题是关于 len 的单峰函数,然后刚好这几天在练三分和二分,所以就写了个三分,但是后来开网后发现这个不是三分函数,于是改成了二分之后过了。

点击查看代码
double check(double x) {
	for (int i = 1; i <= n; i ++) {
		sum[i] = sum[i - 1] + w[i] * 1.0 - x;
	}
	double t = 0;
	for (int i = k; i <= n; i ++) {
		if (sum[i] > t) return true;
		t = min(t, sum[i - k + 1]);
	}
	return false;
}

int main() {
	freopen("qwq.in","r",stdin);
	n = fr(), k = fr();
	for (int i = 1; i <= n; i ++) {
		w[i] = fr();
	}
	double l = 1, r = 1e6;
	while (r - l > eps) {
		double mid = (r + l) / 2;
		if (check(mid)) l = mid;
		else r = mid;
	}
	printf("%.6lf",l);
	return 0;
}

P7150 Stuck in a Rut S

题目传送门

 看题解好像都是用拓扑写的,但是我直接贪心(应该算吧),就是把所有往下走的按照 y 排一个序,所有往左走的按照 x 排一个序,然后如果前面这个牛碰撞了,那么就说明后面这个牛不会被碰撞。

 本来可以一百的,但是因为有一句话忘记删了导致我的主函数跑了 n 遍,下次一定好好检查,顺带样例数据可不可以强一点!!

点击查看代码
struct node {
	bool type;
	int x,y;
	int id;
	
	bool operator < (const node t) const {
		if (type == t.type) {
			if (type) return x < t.x;
			return y < t.y;
		}
		return type < t.type;
	}
};

bool cmp(int a,int b) {
	return w[a].id < w[b].id;
}

int main() {
	n = fr();
	string s;
	int cnt = 0;
	for (int i = 1; i <= n; i ++) {
		cin >> s;
		if (s[0] == 'N') w[i].type = 1;
		else cnt ++;
		w[i].x = fr(), w[i].y = fr();
		w[i].id = i;
	}
	// 0 => x ++ 
	// 1 => y ++
	sort(w + 1, w + 1 + n);
	for (int i = 1; i <= n; i ++) {
		id[w[i].id] = i;
	}
	for (int i = 1; i <= cnt; i ++) {
		// i => x ++    j => y ++
		for (int j = cnt + 1; j <= n; j ++) {
			if (flag[j] || w[i].y < w[j].y || w[j].x < w[i].x)
				continue;
			int t1 = w[j].x - w[i].x, t2 = w[i].y - w[j].y;
			if (t1 > t2) {
				flag[i] = true;
				ans[j] += ans[i] + 1;
				break;
			} else if (t1 < t2) {
				flag[j] = true;
				ans[i] += ans[j] + 1;
			}
		}
	}
	for (int i = 1; i <= n; i ++) {
		fw(ans[id[i]]);
		ch;
	}
	return 0;
}

Cheap Robot

题目传送门

 感觉是一个图论杂烩题,题目要求求电量最小值,我们就考虑两个点 ij ,从 ij 的电池容量所需的其实就是 i 到 最近的充电桩的距离加上 j 到最近的充电桩的距离再加上两点之间的距离。

 这个地方求每个点到充电桩的最小距离可以建一个虚拟源点容纳后跑 dij 。然后我们可以把每个边的边权都换成这个值。

 然后很明显,从 uv 所需的最小电容量就是一路上最大的所需电量,于是乎就可以想到用 kruskal 重构树来解决这个问题。

 询问的时候直接求一个 LCA 然后输出那个点的权值就可以了。

点击查看代码
struct node {
	int v;
	lwl w;
};

struct Node {
	int a,b;
	lwl w;
	
	bool operator < (const Node t) const {
		return w < t.w;
	}
}E[N * 3];

int n, m, K, Q;
int idx;
vector<node> edge[N];
vector<int> e[N];
lwl dis[N];
lwl h[N],w[N];
bool flag[N];
int fa[N][30], de[N];

void dij(int st) {
	memset(flag,0,sizeof flag);
	priority_queue<pii,vector<pii>,greater<pii> > q;
	dis[st] = 0;
	q.push({dis[st],st});
	while (q.size()) {
		auto u = q.top().se;
		q.pop();
		
		if (flag[u]) continue;
		flag[u] = true;
		
		for (auto it : edge[u]) {
			int v = it.v;
			lwl w = it.w;
			if (flag[v]) continue;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({dis[v],v});
			}
		}
	}
}

int find(int x) {
	if (x != h[x]) h[x] = find(h[x]);
	return h[x];
}

void kruskal() {
	for (int i = 1; i <= n * 2; i ++) h[i] = i;
	sort(E + 1, E + 1 + m);
	idx = n;
	for (int i = 1; i <= m; i ++) {
		int a = E[i].a, b = E[i].b;
		lwl W = E[i].w;
		int ha = find(a), hb = find(b);
		if (ha != hb) {
			w[++ idx] = W;
			h[ha] = idx, h[hb] = idx;
			e[idx].push_back(ha);
			e[idx].push_back(hb);
		}
	}
}

void dfs(int u,int p) {
	fa[u][0] = p;
	de[u] = de[p] + 1;
	for (int k = 1; k <= 25; k ++) 
		fa[u][k] = fa[fa[u][k - 1]][k - 1];
	for (auto v : e[u]) {
		if (v == p) continue;
		dfs(v,u);
	}
}

int LCA(int x,int y) {
	if (de[x] < de[y]) swap(x,y);
	
	int dh = de[x] - de[y];
	for (int k = 25; k >= 0; k --) {
		if (dh & (1 << k)) {
			x = fa[x][k];
		}
	}
	
	if (x == y) return x;
	
	for (int k = 25; k >= 0; k --) {
		if (fa[x][k] != fa[y][k]) {
			x = fa[x][k];
			y = fa[y][k];
		}
	}
	return fa[x][0];
}

int main() {
	memset(dis,0x3f,sizeof dis);
	n = fr(), m = fr(), K = fr(), Q = fr();
	for (int i = 1; i <= m; i ++) {
		int a = fr(), b = fr(), w = fr();
		edge[a].push_back({b,w});
		edge[b].push_back({a,w});
		E[i] = {a,b,w};
	}
	for (int i = 1; i <= K; i ++) {
		edge[0].push_back({i,0});
	}
	dij(0);
	for (int i = 1; i <= m; i ++) {
		E[i].w += dis[E[i].a] + dis[E[i].b];
	}
	kruskal();
	dfs(idx,0);
	while (Q --) {
		int a = fr(), b = fr();
		int t = LCA(a,b);
		fw(w[t]);
		ch;
	}
	return 0;
}

Day 46

P4343 自动刷题机

题目传送门

 直接二分,因为这个 n 越大,能够做的题就越少,所以直接二分出一个左端点一个右端点,然后判断右端点是不是大于等于右端点(判断有没有解)

点击查看代码
lwl check(lwl t) {
	lwl u = 0, cnt = 0;
	for (int i = 1; i <= l; i ++) {
		u = max(u + w[i],0ll);
		if (u >= t) cnt ++, u = 0;
	}
	return cnt;
}

int main() {
	lwl sum = 0;
	l = fr(), k = fr();
	for (int i = 1; i <= l; i ++) {
		w[i] = fr();
		sum += max(w[i],0);
	}
	lwl l = 1, r = sum;
	lwl minn = sum, maxn = 0;
	while (l <= r) {
		lwl mid = (l + r) >> 1;
		if (check(mid) <= k) minn = mid, r = mid - 1;
		else l = mid + 1;
	}
	l = 1, r = sum;
	while (l <= r) {
		lwl mid = (l + r) >> 1;
		if (check(mid) >= k) maxn = mid, l = mid + 1;
		else r = mid - 1;
	}
	if (maxn < minn) wj;
	else {
		fw(minn),kg;
		fw(maxn),ch;
	}
	return 0;
}

P7961 数列

题目传送门

 这个题目一开始做的时候想对了,但是做的过程非常崎岖。首先是一开始看错了,看成等于 k1 ,所以第二个样例死过不去,然后又仔细看了一遍题,找到了不对的地方,然后就放着了,结果交上去一看只有 35 分,就以为做法错了,改了做法之后交了 AC 了,结果仔细一看数据范围,发现有一个数组开小了,改了就过了。恼。

 用一个 dp 数组,存用了几位,这一位放了几个,几个 1 ,还有上一位的进位是多少。

 于是乎就可以得到转移方程式:

dp[u][j][t][k+((p+t)&1)][p+t]=dp[u][j1][t1][k+((p+t1)&1)][p+t1]w[i]

 然后当 t 等于 0 的话就是从上一位转移过来,这个时候就要有上一位的和,然后稍微推一下就可以得到这个式子

dp[u][j][t][k][p]C[n(jt)][t]

 这个可以在上一位的时候就提前计算出来,最后统计答案的时候不要忘记算上 p 里面的 1

点击查看代码
int n,K,m;
lwl w[M];
lwl C[N][N];
// 到哪个 用了几位 这一个放了几个 几个1
// 上一位多少
lwl dp[2][N][N][N][N];
lwl sum[2][N][N][N];
// 权值和

int cnt(int a) {
	int count  = 0;
	while (a) {
		count ++;
		a -= (a & - a);
	}
	return count;
}

void init() {
	C[0][0] = 1;
	for (int i = 1; i <= n; i ++) C[i][0] = 1;
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= i; j ++) {
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
		}
	}
}

signed main() {
	n = fr(), m = fr(), K = fr();
	init();
	for (int i = 1; i <= m + 1; i ++) w[i] = fr();
	sum[0][0][0][0] = 1;
	for (int i = 1; i <= m + 1; i ++) {
		int u = i & 1, v = 1 - u;
		for (int j = 0; j <= n; j ++) {
			for (int t = 0; t <= j; t ++) {
				for (int p = 0; p <= j; p ++) {
					for (int k = 0; k <= K; k ++) {
						if (!t) {
							dp[u][j][t][k + (p & 1)][p] = (sum[v][j][k][p << 1] + sum[v][j][k][(p << 1) | 1]) % mod;
							continue;
						}
						dp[u][j][t][k + ((p + t) & 1)][p + t] = dp[u][j - 1][t - 1][k + ((p + t - 1) & 1)][p + t - 1] * w[i] % mod;
					}
				}
			}
		}
		memset(sum[u],0,sizeof sum[u]);
		for (int j = 0; j <= n; j ++) {
			for (int t = 0; t <= j; t ++) {
				for (int p = 0; p <= n; p ++) {
					for (int k = 0; k <= K; k ++) {
						sum[u][j][k][p] = (sum[u][j][k][p] + dp[u][j][t][k][p] * C[n - (j - t)][t] % mod) % mod;
					}
				}
			}
		}
	}
	lwl ans = 0;
	for (int p = 0; p <= n; p ++) {
		for (int k = 0; k <= K; k ++) {
			if (k + cnt(p >> 1) <= K)
				ans = (ans + sum[(m + 1) & 1][n][k][p]) % mod;
		}
	}
	fw(ans);
	return 0;
}

P2331 最大子矩阵

题目传送门

 因为这一题的数据范围很小,所以可以 n4 瞎搞,但是可以写成 n2 的,但是我懒得写了。

 因为 m 最多只有两列,如果只有一列的情况就直接一个线性 dp 可以轻松搞定,关键就是两列的情况。

 两列的时候,就枚举两列分别用到了哪里,到现在为止有多少个矩阵。那么对于左边这列在 i,右边这列在 j 的情况可以列出下面四个转移方程式:

max(dp[i1][j][t],dp[i][j1][t]) (有一个格子不选的情况)

{max(dp[p][j][t1]+sum(p,i,1))p<imax(dp[i][p][t1]+sum(p,j,2))p<j

 这个的意思就是单列新开一个矩阵

dp[p][p][t1]+sum(p,i,1)+sum(p,j,2) (要求 i==j ,意思是两列一起合起来的一个矩阵)

点击查看代码
int n, m, k;
int w[N][M],sum[N][M];
int dp[N][N][N];

int get(int l,int r,int op) {
	return sum[r][op] - sum[l][op];
}

void mx(int &a, int b) {
	if (a < b) a = b;
}

int main() {
	n = fr(), m = fr(), k = fr();
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) {
			w[i][j] = fr();
			sum[i][j] = sum[i - 1][j] + w[i][j];
		}
	}
	if (m == 1) {
		memset(dp,-0x3f,sizeof dp);
		dp[0][0][0] = 0;
		for (int i = 1; i <= n; i ++) {
			int j = i - 1;
			dp[i][0][0] = 0;
			for (int t = 1; t <= k; t ++) {
				dp[i][t][1] = max({dp[j][t - 1][1],dp[j][t - 1][0],dp[j][t][1]}) + w[i][1];
				dp[i][t][0] = max(dp[j][t][0],dp[j][t][1]);
			}
		}
		fw(max(dp[n][k][1],dp[n][k][0]));
		ch;
		return 0;
	}
	for (int t = 1; t <= k; t ++) {
		for (int i = 1; i <= n; i ++) {
		// 第一列
			for (int j = 1; j <= n; j ++) {
			// 第二列
				dp[i][j][t] = max(dp[i - 1][j][t],dp[i][j - 1][t]);
				for (int p = 0; p < max(i,j); p ++) {
					if (p < i) {
						mx(dp[i][j][t],dp[p][j][t - 1] + get(p,i,1));
					}
					if (p < j) {
						mx(dp[i][j][t],dp[i][p][t - 1] + get(p,j,2));
					}
					if (i == j) {
						mx(dp[i][j][t],dp[p][p][t - 1] + get(p,i,1) + get(p,j,2));
					}
				}
			}
		}
	}
	fw(dp[n][n][k]);
	return 0;
}

Random Walk to Millionaire

题目传送门

 期望

 用一下完全平方式来记录和计算然后直接 bfs 就可以直接冒过去了。

点击查看代码
lwl ans;
int n,m,k;
vector<int> e[N];
bool flag[N];
bool vis[N][N];
lwl d[N];
lwl dp[3][N][N];
// 0 => x ^ 2 * p   1 => x * p   2 => p
// 哪个点      时间点

lwl ksm(lwl a,lwl k) {
	lwl ans = 1;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

lwl ny(lwl a) {
	return ksm(a,mod - 2);
}

void bfs() {
	queue<pii> q;
	q.push({1,0});
	dp[2][1][0] = 1;
	while (q.size()) {
		int u = q.front().fi;
		int t = q.front().se;
		q.pop();
		
		for (auto v : e[u]) {
			dp[0][v][t + 1] += dp[0][u][t] * d[u] % mod;
			dp[0][v][t + 1] %= mod;
			dp[1][v][t + 1] += dp[1][u][t] * d[u] % mod;
			dp[1][v][t + 1] %= mod;
			dp[2][v][t + 1] += dp[2][u][t] * d[u] % mod;
			dp[2][v][t + 1] %= mod;
			
			if (flag[v]) {
				// + x ^ 2
				ans += (dp[0][u][t] * d[u]) % mod;
				ans %= mod;
			} else {
				// x ++
				dp[0][v][t + 1] += 2 * dp[1][u][t] * d[u] % mod;
				dp[0][v][t + 1] += dp[2][u][t] * d[u] % mod;
				dp[0][v][t + 1] %= mod;
				dp[1][v][t + 1] += dp[2][u][t] * d[u] % mod;
				dp[1][v][t + 1] %= mod;
			}
			
			if (t + 1 < k && !vis[v][t + 1]) {
				vis[v][t + 1] = true;
				q.push({v,t + 1});
			}
		}
	}
}

int main() {
	n = fr(), m = fr(), k = fr();
	for (int i = 1; i <= m; i ++) {
		int a = fr(), b = fr();
		e[a].push_back(b);
		e[b].push_back(a);
		d[a] ++, d[b] ++;
	}
	for (int i = 1; i <= n; i ++) flag[i] = fr();
	for (int i = 1; i <= n; i ++) {
		d[i] = ny(d[i]);
	}
	bfs();
	fw(ans);
	return 0;
}

Day 47

 今天的题目都是恶心码量模拟题(除了一道小清新换根题)

P9436 一些数

题目传送门

点击查看代码
int main() {
	int T = fr();
	while (T --) {
		lwl ans = 0;
		n = fr(), m = fr();
		int cc = 0;
		int tt = 0;
		for (int i = 1; i <= n; i ++)
			qwq[i] = 0,w[i] = 0,sum[i] = 1;
		for (int i = 1; i <= m; i ++) {
			int a = fr(), b = fr();
			q[i] = {a, b};
			w[a] = b;
			sum[b] = 0;
			if (abs(a - b) >= 2) cc ++, tt = a;
		}
		for (int i = 1; i <= n; i ++) sum[i] += sum[i - 1];
		if (cc) {
			if (cc > 1) wj;
			else {
				int t = (w[tt] > t) * 2 - 1;
				bool flag = true;
				for (int i = 1; i <= n; i ++) {
					if (abs(w[i] - i) >= 2) continue;
					if (w[i]) continue;
					if (tt < i) {
						if (w[i] != i + tt && w[i]) {
							flag = false;
							break;
						}
					} else {
						if (w[i] != i && w[i]) {
							flag = false;
							break;
						}
					}
				}
				if (flag) puts("1");
				else puts("1");
			}
			continue;
		}
		if (!m) {
			fw((lwl)n * (n - 1) - n + 1);
			ch;
			continue;
		}
		cc = 0;
		for (int i = 1; i <= n; i ++) {
			if (!w[i]) continue;
			cc += (w[i] != i);
		}
		q[0].fi = 0,q[m + 1].fi = n + 1;
		if (!cc) {
			for (int i = 1; i <= m + 1; i ++) {
				int len = q[i].fi - q[i - 1].fi - 1;
				if (!len) continue;
				ans += (lwl)len * (len - 1) - len + 1;
			}
			fw(ans);
			ch;
			continue;
		}
		sort(w + 1, w + 1 + m);
		int l = m, r = 1;
		for (int i = 1; i <= m; i ++) {
			if (q[i].fi != q[i].se) {
				l = i;
				break;
			}
		}
		for (int i = m; i; i --) {
			if (q[i].fi != q[i].se) {
				r = i;
				break;
			}
		}
		if (q[l].fi < q[l].se) {
			bool flag = true;
			for (int i = l; i <= r; i ++) {
				if (q[i].fi != q[i].se - 1) {
					flag = false;
					break;
				}
			}
			if (flag) {
				ans = (lwl)(q[l].fi - q[l - 1].fi) * (q[r + 1].fi - q[r].fi - 1);
				fw(ans);
				ch;
				continue;
			} else {
				flag = true;
				for (int i = l + 1; i <= n; i ++) {
					if (q[i].fi != q[i].se + 1) {
						flag = false;
						break;
					}
				}
				if (flag) {
					ans = (q[l].se >= q[r].fi && q[l].se < q[r + 1].fi);
					fw(ans);
					ch;
					continue;
				}
			}
		}
		if (q[r].fi > q[r].se) {
			bool flag = true;
			for (int i = l; i <= r; i ++) {
				if (q[i].fi != q[i].se + 1) {
					flag = false;
					break;
				}
			}
			if (flag) {
				ans = (lwl)(q[l].fi - q[l - 1].fi - 1) * (q[r + 1].fi - q[r].fi);
				fw(ans);
				ch;
				continue;
			} else {
				flag = true;
				for (int i = l; i <= r - 1; i ++) {
					if (q[i].fi != q[i].se - 1) {
						flag = false;
						break;
					}
				}
				if (flag) {
					ans = (q[r].se <= q[l].fi && q[r].se > q[l - 1].fi);
					fw(ans);
					ch;
					continue;
				}
			}
		}
		wj;
	}
	return 0;
}

Isolate Elements

题目传送门

 因为是一行行变的,所以考虑将每一行作为一个整体,然后像玉米田一样处理(大概吧)

 然后直接暴力判断状态就可以了。

点击查看代码
int main() {
	n = fr(), m = fr();
	for (int i = 1; i <= n; i ++) {
		w[i][0] = w[i][m + 1] = inf;
		for (int j = 1; j <= m; j ++) {
			w[i][j] = fr();
		}
	}
	for (int j = 1; j <= m; j ++) w[0][j] = inf,w[n + 1][j] = inf;
	memset(dp,0x3f,sizeof dp);
	dp[1][1][1] = dp[1][0][1] = 1;
	dp[1][0][0] = dp[1][1][0] = 0;
	for (int i = 2; i <= n + 1; i ++) {
		int type = -1;
		int u = i - 1;
		// 上上行不翻转 上行不翻转
		for (int j = 1; j <= m; j ++) {
			if (w[u][j] != w[u][j - 1] && w[u][j] != w[u][j + 1] &&
				w[u][j] != w[u - 1][j]) {
				int tmp = -1;
				if (w[u][j] == w[i][j]) tmp = 0;
				else tmp = 1;
				if (type == -1) type = tmp;
				else if (type != tmp) type = inf;
			}
		}
		if (type != inf && ~ type)
			dp[i][0][type] = min(dp[i][0][type],dp[u][0][0] + type);
		else if (type == -1) {
			dp[i][0][1] = min(dp[i][0][1],dp[u][0][0] + 1);
			dp[i][0][0] = min(dp[i][0][0],dp[u][0][0]);
		}
		type = -1;
		// 上上行翻转 上行不翻转
		for (int j = 1; j <= m; j ++) {
			if (w[u][j] != w[u][j - 1] && w[u][j] != w[u][j + 1] &&
				1 - w[u][j] != w[u - 1][j]) {
				int tmp = -1;
				if (w[u][j] == w[i][j]) tmp = 0;
				else tmp = 1;
				if (type == -1) type = tmp;
				else if (type != tmp) type = inf;
			}
		}
		if (type != inf && ~ type)
			dp[i][0][type] = min(dp[i][0][type],dp[u][1][0] + type);
		else if (type == -1) {
			dp[i][0][1] = min(dp[i][0][1],dp[u][1][0] + 1);
			dp[i][0][0] = min(dp[i][0][0],dp[u][1][0]);
		}
		type = -1;
		// 上上行翻转 上行翻转
		for (int j = 1; j <= m; j ++) {
			if (w[u][j] != w[u][j - 1] && w[u][j] != w[u][j + 1] &&
				w[u][j] != w[u - 1][j]) {
				int tmp = -1;
				if (1 - w[u][j] == w[i][j]) tmp = 0;
				else tmp = 1;
				if (type == -1) type = tmp;
				else if (type != tmp) type = inf;
			}
		}
		if (type != inf && ~ type)
			dp[i][1][type] = min(dp[i][1][type],dp[u][1][1] + type);
		else if (type == -1) {
			dp[i][1][1] = min(dp[i][1][1],dp[u][1][1] + 1);
			dp[i][1][0] = min(dp[i][1][0],dp[u][1][1]);
		}
		type = -1;
		// 上上行不翻转 上行翻转
		for (int j = 1; j <= m; j ++) {
			if (w[u][j] != w[u][j - 1] && w[u][j] != w[u][j + 1] &&
				1 - w[u][j] != w[u - 1][j]) {
				int tmp = -1;
				if (1 - w[u][j] == w[i][j]) tmp = 0;
				else tmp = 1;
				if (type == -1) type = tmp;
				else if (type != tmp) type = inf;
			}
		}
		if (type != inf && ~ type)
			dp[i][1][type] = min(dp[i][1][type],dp[u][0][1] + type);
		else if (type == -1) {
			dp[i][1][1] = min(dp[i][1][1],dp[u][0][1] + 1);
			dp[i][1][0] = min(dp[i][1][0],dp[u][0][1]);
		}
	}
	int ans = min(dp[n + 1][1][0],dp[n + 1][0][0]);
	if (ans > n) wj;
	else fw(ans);
	return 0;
}

P8188 Email Filing S

题目传送门

 直接用优先队列模拟。

点击查看代码
int main() {
	int T = fr();
	while (T --) {
		n = fr(), m = fr(), k = fr();
		memset(cnt,0,sizeof cnt);
		memset(flag,0,sizeof flag);
		for (int i = 1; i <= m; i ++) {
			h[i] = fr();
			cnt[h[i]] ++;
			flag[i] = false;
		}
		bool vis = true;
		priority_queue<pii,vector<pii>,greater<pii> > q;
		// 窗口里面的邮件
		deque<int> t; // 跳过的邮件
		for (int i = 1; i <= k; i ++)
			q.push({h[i],i});
		int l = 1, r = k, u = 1;
		while (q.size() || t.size()) {
			while (!cnt[u] && u < n - k + 1)
				u ++;
			if (q.size() && q.top().fi >= u && q.top().fi <= u + k - 1) {
				cnt[q.top().fi] --;
				flag[q.top().se] = true;
				q.pop();
				if (r < m) {
					r ++;
					q.push({h[r],r});
				}
			} else if (r < m) {
				// 窗口往下滑
				r ++;
				q.push({h[r],r});
				t.push_back(h[l]);
				l ++;
			} else if ((int)q.size() < k && t.size()) {
				l --;
				q.push({t.back(),l});
				t.pop_back();
			} else {
				vis = false;
				break;
			}
			while (q.size() && q.top().se < l)
				q.pop();
			while (flag[l]) l ++;
		}
		if (vis && q.empty()) yj;
		else wj;
	}
	return 0;
}

P3047 Nearby Cows G

题目传送门

 小清新换根 dp ,因为 k 很小,所以直接全部枚举就可以了。

点击查看代码
void dfs(int u,int fa) {
	dp[u][0] = w[u];
	for (auto v : e[u]) {
		if (v == fa) continue;
		dfs(v,u);
		for (int t = k; t; t --) 
			dp[u][t] += dp[v][t - 1];
	}
}

void get(int u,int fa) {
	f[u][0] = w[u];
	for (auto v : e[u]) {
		if (v == fa) continue;
		for (int t = k; t; t --) {
			f[v][t] += f[u][t - 1];
		}
		for (int t = k; t >= 2; t --) {
			f[v][t] += dp[u][t - 1] - dp[v][t - 2];
		}
		get(v,u);
	}
}

int main() {
	n = fr(), k = fr();
	for (int i = 1; i < n; i ++) {
		int a = fr(), b = fr();
		e[a].push_back(b);
		e[b].push_back(a);
	}
	for (int i = 1; i <= n; i ++) 
		w[i] = fr();
	dfs(1,0);
	get(1,0);
	for (int u = 1; u <= n; u ++) {
		int ans = w[u];
		for (int i = 1; i <= k; i ++) {
			ans += dp[u][i] + f[u][i];
		}
		fw(ans);
		ch;
	}
	return 0;
}

Day 48

P9234 买瓜

题目传送门

 折半搜索,复杂度大概是 O(3n2) ,但是 Richard_H 写的搜到底要比我们的快,很恼怒。

 就说一说主要的剪枝:

  1. 排序顺序优化,先从小到大排序再 dfs
  2. 最优性剪枝,如果说有比这个更优的答案,就不继续遍历了
  3. 可行性剪枝,如果说当前的总重量大于所需的总重量就直接返回

 然后因为这一题要砍一半,所以一开始可以将所有重量都乘二来做。

 一开始因为用 map 的时候没有写 h.count() 而是写的是 !h[sum] ,所以把 cnt 等于 0 的点都当成没有遍历过的了。

点击查看代码
void dfs1(int u,lwl sum,int cnt) {
	if (sum > m) return ;
	if (cnt > ans) return ;
	if (h.count(sum) && cnt > h[sum]) return;
	if (sum == m) ans = min(ans,cnt);
	if (u > n / 2) {
		if (!h.count(sum)) h[sum] = cnt;
		else h[sum] = min(h[sum],cnt);
		return ;
	}
	dfs1(u + 1,sum + w[u],cnt);
	dfs1(u + 1,sum + (w[u] >> 1),cnt + 1);
	dfs1(u + 1,sum,cnt);
}

void dfs2(int u,lwl sum,int cnt) {
	if (h.count(sum)) ans = min(ans,h[sum] + cnt);
	if (cnt > ans) return ;
	if (u > n) return ;
	dfs2(u + 1,sum - w[u],cnt);
	dfs2(u + 1,sum - (w[u] >> 1),cnt + 1);
	dfs2(u + 1,sum,cnt);
}

int main(){
	n = fr(), m = fr() << 1;
	for (int i = 1; i <= n; i ++) w[i] = fr() << 1;
	sort(w + 1,w + 1 + n);
	for (int i = 1; i <= n; i ++) {
		s += w[i];
	}
	if (s < m) {
		wj;
		return 0;
	}
	dfs1(1,0,0);
	dfs2(n / 2 + 1,m,0);
	if (ans >= inf / 2) wj;
	else fw(ans);
	return 0;
}

P7382 Simfonija

题目传送门

 应该算是一道思维题?复杂度瓶颈在排序那里。

 可以想到,先修改某个数和先加之后再修改某个数的贡献是一样的,因为最优方案肯定是最后将他变成和 b 数组一样的数。

 所以说我们就是要选择一段 nk 长度的区间,再选择一个数 x ,求 min|ai+xbi|

 选区间的话直接暴力枚举就可以了,对于找到一个数 x 的话,我们可以把 biai 看做一个变量,那么原来的式子就可以转化成这个样子:

|x(biai)|

 这个式子的形式就有点像距离公式,所以问题就转变成了数轴上有 nk 个点,在数轴上找到一个点的距离离这个点的距离和最近。

 毫无疑问,如果数轴上的点的数量是奇数的话(设 wi=biai),那么 x=wn+12。如果数轴上的点是偶数的话,那么 x=(wn2+wn2+1)/2

 所以说先将 biai 排一个序,在枚举区间的过程中就可以 O(1) 求出 x。有了 x 之后求出答案就很容易了(我这里用的是前缀和求)。

点击查看代码
int n,K;
pii w[N];
int c[N];
lwl sum[N];

lwl get(int l,int r) {
	return sum[r] - sum[l - 1];
}

int main(){
	n = fr(), K = fr();
	for (int i = 1; i <= n; i ++) w[i].fi = fr();
	for (int i = 1; i <= n; i ++) w[i].se = fr();
	if (n == K) {
		puts("0");
		return 0;
	}
	for (int i = 1; i <= n; i ++) {
		c[i] = w[i].se - w[i].fi;
	}
	sort(c + 1, c + 1 + n);
	for (int i = 1; i <= n; i ++) {
		sum[i] = sum[i - 1] + c[i];
	}
	int k = n - K;
	lwl ans = linf;
	for (int i = 1; i <= n - k + 1; i ++) {
		int j = i + k - 1;
		int t1 = (i + j) >> 1,t2;
		if ((i + j) & 1) t2 = t1 + 1;
		else t2 = t1;
		lwl x = (c[t1] + c[t2]) >> 1;
		ans = min(ans,(t1 - i + 1) * x - get(i,t1) + get(t2,j) - (j - t2 + 1) * x);
	}
	fw(ans);
	return 0;
}

Grid 2

题目传送门

 一点前置:如果从 (x1,y1)(x2,y2) 并只能向下走或者向右走的方案数是 :Cx2x1+y2y1x2x1 (前提是 x1<=x2 并且 y1<=y2)(在 x2x1+y2y1 中选 x2x1 步向下走)

 假设 dp[i] 为到 i 点并且前面没有经过任何障碍点的方案数,如果说不考虑前面有没有经过障碍点的话,那么就直接 Cw[i].fi1+w[i].se1w[i].fi1

 然后要把前面经过的障碍点都去掉,其实这个就直接 dp[i]=(dp[i]dp[j]C(w[i].fiw[j].fi+w[i].sew[j].se,w[i].fiw[j].fi)

点击查看代码
int n;
int H,W;
pii w[N];
lwl f[M],nf[M];
lwl dp[N];

lwl ksm(lwl a,lwl k) {
	lwl ans = 1;
	while (k) {
		if (k & 1) ans = ans * a % mod;
		a = a * a % mod;
		k >>= 1;
	}
	return ans;
}

lwl ny(lwl a) {
	return ksm(a,mod - 2);
}

void init() {
	f[0] = 1;
	for (int i = 1; i <= m; i ++) {
		f[i] = i * f[i - 1] % mod;
	}
	nf[m] = ny(f[m]);
	for (int i = m - 1; i >= 0; i --) {
		nf[i] = nf[i + 1] * (i + 1) % mod;
	}
}

lwl C(int a,int b) {
	if (!b) return 1;
	return f[a] * nf[a - b] % mod * nf[b] % mod;
}

int main(){
	H = fr(), W = fr(), n = fr();
	for (int i = 1; i <= n; i ++) {
		int a = fr(), b = fr(); 
		w[i] = {a, b};
	}
	init();
	lwl ans = C(H - 1 + W - 1,H - 1); 
	sort(w + 1,w + 1 + n);
	for (int i = 1; i <= n; i ++) {
		dp[i] = C(w[i].fi - 1 + w[i].se - 1,w[i].fi - 1);
		for (int j = 1; j < i; j ++) {
			if (w[i].se < w[j].se) continue;
			dp[i] = (dp[i] - dp[j] * C(w[i].fi - w[j].fi + w[i].se - w[j].se,w[i].fi - w[j].fi) % mod) % mod;
		}
	}
	for (int i = 1; i <= n; i ++) {
		ans = (ans - dp[i] * C(H - w[i].fi + W - w[i].se,H - w[i].fi) % mod) % mod;
	}
	fw((ans + mod) % mod);
	return 0;
}
posted @   jingyu0929  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示