ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[2021省选板刷]

2021-03-30 22:32:08  阅读:148  来源: 互联网

标签:板刷 ll 省选 int 2021 ans include color define


绿色的话主要是自己想出来的,红色的是看了题解的
真就啥都不会。。。

\({\color{red}{「九省联考 2018」一双木棋}}\)

前置知识:\(minmax搜索\):
两方都采取最优策略,问我方的最终结果,轮到我方采取对答案贡献最大的,敌方采取对答案贡献最小的搜索方案
考虑用\(vector\)记录每一列放了多少棋子
记忆化搜索完全可以*过去

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#define ll long long

using std::vector;
using std::map;

map<vector<int>,int>f;
vector<int>h;//状态总数

inline ll read(){
	char a = getchar();
	ll ans = 0,o = 1;
	while(a != '-' && (a < '0' || a > '9'))
	a = getchar();
	if(a == '-')
	o = -1,a = getchar();
	while(a <= '9' && a >= '0')
	ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
	return ans * o; 
}

ll n,m;
int A[12][12],B[12][12];
ll inf = 0x7f7f7f7f;

int dfs(int turn){//对抗搜索
	if(f.find(h) != f.end())return f[h];
	int ans = turn ? -inf : inf;
	for(int i = 0;i < n;++i){
		if((i == 0 || h[i - 1] > h[i]) && h[i] < m){
			++h[i];
			if(turn)
			ans = std::max(ans,dfs(0) + A[i + 1][h[i]]);
			else
			ans = std::min(ans,dfs(1) - B[i + 1][h[i]]);
			--h[i]; 
		}
	}
	return f[h] = ans;
} 

int main(){
	n = read(),m = read();
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= m;++j)
	A[i][j] = read();
	for(int i = 1;i <= n;++i)
	for(int j = 1;j <= m;++j)
	B[i][j] = read();
	for(int i = 0;i < n;++i)
	h.push_back(m);
	f[h] = 0;//无路可走自然是0
	for(int i = 0;i < n;++i)
	h[i] = 0;
	std::cout<<dfs(1); 
}

虽说不开\(-o2\)只有\(80\),但我要是在这次省选里能做到这步也就不错了

[九省联考2018]IIIDX

\({\color{green}{subtask1}}\)无重复数据
考虑每个点只有一个前缀,那么显然构成一颗树形结构。
我们先把这颗树建出来,那么作为父节点要小于每个子节点的值。
那么我们考虑贪心,把更大一点的值留给子节点。
注意一个点:按照我的建边方法,需要从后往前建点,因为对于并列的点来说,我们递归进子树,先遍历的一颗子树的权值会更大。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define N 500005

ll n;
double k;
ll v[N],top;

struct P{
	int to,next;
}e[4 * N];

int cnt,head[N],ans[N];

void add(int x,int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}

ll root;

void dfs(int u){
	for(int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		dfs(v);
	}
	if(u != root)
	ans[u] = v[top--];
}

int main(){
	scanf("%lld",&n);
	scanf("%lf",&k);
	for(int i = 1;i <= n;++i)
	scanf("%lld",&v[i]);
	std::sort(v + 1,v + n + 1);
	root = n + 1;
	top = n;
	for(int i = n;i >= 1;--i){
		int dad = i / k;
		dad = dad ? dad : root;
		add(dad,i);
	}
	dfs(root);
	for(int i = 1;i <= n;++i)
	printf("%d ",ans[i]);
}

\({\color{red}{subtask2}}\)有重复数据
那么明显我们第一档的贪心是不满足答案的
我们考虑从小到大排序,那么根据我们的字典序最优法则
字典序最大的一类题目:
正解则是考虑按位确定:考虑从 \(1\) 到 \(n\) 依次确定该点选了哪个值,确定 i 这个位置的值时,我们选取满足以下条件的值:

  1. 使得这个东西确定之后,存在一种 \(i+1\) 到 \(n\) 的分配方案,使得总方案合法。
  2. 尽可能大。
    我们考虑如何判断在这一位选定的数\(x\),我们要使大于等于\(x\)的数的数量大于等于\(siz_i\)
    这个时候我们记\(f_i\)为大于第\(i\)个数的数量
    考虑进行二分求解\(x\)
    这个时候在\(1~n\)全局减去\(siz_x\)(先给子树站好位置,防止其他的父亲抢儿子)
    如果遇到了\(i\)的儿子,那么在\(res[fa[i]] ~ n\)上加回\(siz_x\)(拿回位置,由于子树是连续一串,所以不会影响贡献)
    \(f_i\)在操作过程中可能不再满足单调性,但这是由于我们是按位确定,确定完父亲后并不能判断后续儿子的情况,实际上\(f_i\)是会满足单调性的
    那么我们进行一个取\(min\)操作,操作得到的数就是最好情况下\(f_i\)最多的值
    代码鸽了。

\({\color{red}{[九省联考2018]秘密袭击coat}}\)

考虑对每个点进行处理,对于一个点来说,他只有两种选择,选入联通块或者不。
记录\(f_{i,j}\)为当前处理\(S\)是\(s->i\)路径上联通块中排名为\(j\)的联通块方案数

#include<bits/stdc++.h>
#define ll long long
#define N 2000
#define mod 64123

int n,k,w;

int d[N];
int f[N][N],ans;

struct P{
	int to,next;
}e[N << 1];

int cnt,head[N];

void add(int x,int y){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	head[x] = cnt;
}

ll S;

inline int read(){
	char a = getchar();
	int ans = 0,f = 1;
	while(a != '-' && (a < '0' || a > '9'))
	a = getchar();
	if(a == '-')
	f = -1,a = getchar();
	while(a <= '9' && a >= '0')
	ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
	return ans * f; 
}

void dfs(int x,int fa){
	if(d[x] > d[S] || (d[x] == d[S] && x < S))//在集合里选上这个点的贡献 
	for(int i = 1;i < k;++i)
	f[x][i + 1] = f[fa][i];
	else
	for(int i = 1;i <= k;++i)
	f[x][i] = f[fa][i];
	for(int i = head[x];i;i = e[i].next){
		int v = e[i].to;
		if(v == fa)
		continue;
		dfs(v,x);
	}
	for(int i = 1;i <= k;++i)//更新父亲,此时相当于父亲不选
		f[fa][i] = (f[fa][i] + f[x][i]) % mod;
}

int main(){
	n = read(),k = read(),w = read();
	for(int i = 1;i <= n;++i)
	d[i] = read();
	for(int i = 1;i < n;++i){
		int x = read(),y = read();
		add(x,y);
		add(y,x);
	}
	for(int i = 1;i <= n;++i){
		S = i;
		int tot = 0;
		for(int j = 1;j <= n;++j){
			if(d[j] > d[i] || (d[j] == d[i] && i > j))
			tot ++ ;
		}
		if(tot < k - 1) continue;
		std::memset(f,0,sizeof(f));
		f[i][1] = 1;
		for(int j = head[i];j;j = e[j].next)
		dfs(e[j].to,i);
		ans = (ans + 1ll * f[i][k] * d[i]) % mod;
	}
	printf("%lld\n",ans);
}

\({\color{red}{[九省联考2018]劈配}}\)

首先第一档\(d = 1\)明显乱做。。
考虑对每个点每一档的老师都连边,跑网络流,如果有流量那么这一档可以,否则删掉这些老师的边,下一档
如果找到老师,那么对每个老师都和源点连边,如果还有流量,那么证明这个老师其实还有一种方案可以让他选择,所以记录每个老师最后的有方案选择的时间就行了
代码鸽了

\([八省联考2018]林克卡特树\)

题意:大概意思是求树上\(k + 1\)条链的最大和

\({\color{green}{60分}}\)

其实不能全算自己想出来的,对于这类树上的链有经典做法,今天才算学到。
\(f[u][j][0/1/2]\)代表\(u\)中有\(j\)条完整的链,\(0\)是该子树的最优答案,\(1\)是以\(u\)为起点的一条链(这是唯一的不是完整的链的情况),\(2\)是\(u\)处于一条链的中心。
转移看代码。

#include<bits/stdc++.h>
#define ll long long
#define N 300005
#define K 200

int n,k;
int f[N][K][3];

struct P{
	int to,next,v;
}e[N << 1];

int head[N],cnt;

void add(ll x,ll y,ll v){
	e[++cnt].to = y;
	e[cnt].next = head[x];
	e[cnt].v = v;
	head[x] = cnt;
}

void dfs(int u,int fa){
	f[u][0][0] = f[u][0][1] = f[u][1][2] = 0;
	for(int i = head[u];i;i = e[i].next){
		int v = e[i].to;
		if(v == fa)
		continue;
		dfs(v,u);
		for(int j = k;j;--j){
			f[u][j][1] = std::max(f[u][j][1],f[u][j][0] + f[v][0][1] + e[i].v);
			for(int l = j - 1;~l;--l){
				f[u][j][0] = std::max(f[u][j][0],f[u][l][0] + f[v][j - l][0]);
				f[u][j][1] = std::max(f[u][j][1],std::max(f[u][l][1] + f[v][j - l][0],f[u][l][0] + f[v][j - l][1] + e[i].v));
				f[u][j][2] = std::max(f[u][j][2],std::max(f[u][l][2] + f[v][j - l][0],f[u][l][1] + f[v][j - l - 1][1] + e[i].v));
			}
		}
		f[u][0][1] = std::max(f[u][0][1],f[v][0][1] + e[i].v);
	}
	for(int i = 1;i <= k;++i)
	f[u][i][0] = std::max(f[u][i][0],std::max(f[u][i - 1][1],f[u][i][2]));
}

int main(){
	scanf("%d%d",&n,&k),++k;
	for(int i = 1;i < n;++i){
		ll x,y,v;
		scanf("%lld%lld%lld",&x,&y,&v);
		add(x,y,v);
		add(y,x,v);
	}
	memset(f,~0x3f,sizeof(f));dfs(1,0);
	std::cout<<f[1][k][0]<<std::endl;
}

\({\color{red}{WQS二分}}\)

前置芝士:wqs二分
看到这类限制取\(m\)个的最大值或者最小值,如果没有\(m\)限制的\(dp\)复杂度很低,而且关于原\(dp\)是一个凸函数考虑\(wqs二分\)
看完上面那个博客就知道该怎么写了

#include<bits/stdc++.h>
#define reg register
typedef long long ll;
using namespace std;
const int MN=3e5+5;
int to[MN<<1],nxt[MN<<1],c[MN<<1],h[MN],cnt;
inline void ins(int s,int t,int w){
	to[++cnt]=t;nxt[cnt]=h[s];c[cnt]=w;h[s]=cnt;
	to[++cnt]=s;nxt[cnt]=h[t];c[cnt]=w;h[t]=cnt;
}
#define chkmax(a,b) ((a)<(b)?(a)=(b):0)
int n,k;
ll l,r,mid;
struct data{
	ll val;int pos;
	data(ll x=0,int y=0):val(x),pos(y){}
	friend bool operator<(data a,data b){
		ret###${\color{red}{WQS二分}}$urn a.val==b.val?a.pos>b.pos:a.val<b.val;
	}
	friend data operator+(data a,data b){
		return data(a.val+b.val,a.pos+b.pos);
	}
	friend data operator+(data a,ll b){
		return data(a.val+b,a.pos);
	}
}f[MN][3],tmp;
int fa[MN];
void getf(int st){
	for(reg int i=h[st];i;i=nxt[i])
		if(to[i]!=fa[st])fa[to[i]]=st,getf(to[i]);
}
void dfs(int st){
	f[st][0]=f[st][1]=f[st][2]=data();
	chkmax(f[st][2],tmp);
	for(reg int i=h[st];i;i=nxt[i]){
		if(to[i]==fa[st])continue;dfs(to[i]);
		chkmax(f[st][2],max(f[st][2]+f[to[i]][0],f[st][1]+f[to[i]][1]+c[i]+tmp));
		chkmax(f[st][1],max(f[st][1]+f[to[i]][0],f[st][0]+f[to[i]][1]+c[i]));
		chkmax(f[st][0],f[st][0]+f[to[i]][0]);
	}
	chkmax(f[st][0],max(f[st][1]+tmp,f[st][2])); 
}
int main(){
	scanf("%d%d",&n,&k);k++;
	for(reg int i=1,s,t,w;i<n;i++)
		scanf("%d%d%d",&s,&t,&w),ins(s,t,w);
	l=-1e12;r=1e12;getf(1);
	while(l<r){
        mid=(double)(l+r)/2-0.5;
        tmp=data(-mid,1);dfs(1);
        if(f[1][0].pos==k){
            printf("%lld\n",f[1][0].val+mid*k);
            return 0;
        }
        if(f[1][0].pos>k)l=mid+1;
        else r=mid;
    }
	mid=l;tmp=data(-mid,1);dfs(1);
	printf("%lld\n",f[1][0].val+mid*k);
	return 0;
}

\({\color{red}{[十二省联考2019]}}{\color{green}{[春节十二响]}}\)

为啥是双色的?
S-我想到了启发式合并,没仔细想具体过程。
考虑两条链的合并,把两条链丢进堆里,只保留两条链的最大值进结果链,再把较长的链的最后部分塞进结果链
考虑直接把长链当做结果链,那么考虑启发式合并。
解决

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 6;
int n, a[N], f;
vector<int> e[N], o;
priority_queue<int> q[N];

inline void merge(int x, int y) {
	if (q[x].size() < q[y].size()) swap(q[x], q[y]);
	while (q[y].size()) {
		o.push_back(max(q[x].top(), q[y].top()));
		q[x].pop(), q[y].pop();
	}
	while (o.size()) q[x].push(o.back()), o.pop_back();
}

void dfs(int x) {
	for (unsigned int i = 0; i < e[x].size(); i++)
		dfs(e[x][i]), merge(x, e[x][i]);
	q[x].push(a[x]);
}

int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 2; i <= n; i++) scanf("%d", &f), e[f].push_back(i);
	dfs(1);
	long long ans = 0;
	while (q[1].size()) ans += q[1].top(), q[1].pop();
	cout << ans << endl;
	return 0;
}

\({\color{red}{[[省选联考 2020 A/B 卷] 冰火战士]}}\)

想到了一些性质
不过没抽象出本质的东西。
如果两个函数一个单调增,一个单调减,如果两个函数相交是理论最大值。
但是由于两个函数是离散的,可以考虑这样一个东西。
那么答案是如下两个\(k\)的最大值:
\(f_1(max(k)),f_1(k) < f_2(k)\)
\(f_1(min(k)),f_1(k) >= f_2(k)\)
由于函数性质,所以可以进行二分。
如果用树状数组二分的话,有两个\(log\)直接被卡到\(60\)
考虑树状数组倍增,对于这样一个性质树状数组\([d + 2 ^ i]\)计算的正是\([d + 1,d + 2 ^ i]\)\(d是2的整数次幂\)

#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; i++)
using namespace std;

const int N = 2e6 + 10;
int Q, n;
int h[N], tot, suf[N];

struct atom {
    int id, x, y;
} star[N];

struct BIT {
    int C[N];
    int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int v) {
        for (; x <= tot; x += lowbit(x))
            C[x] += v;
    }
    int ask(int x) {
        int ret = 0;

        for (; x; x -= lowbit(x))
            ret += C[x];

        return ret;
    }
} ice, fire;

int calc(int t) {
    if (t < 1 || t > tot)
        return -1;

    return min(ice.ask(t), fire.ask(tot) - fire.ask(t - 1));
}

int main() {
    cin >> Q;
    int op, k, x, y;
    rep(i, 1, Q) {
        scanf("%d%d", &op, &k);
        if (op == 1) {
            scanf("%d%d", &x, &y);
        } else {
            x = star[k].x, y = -star[k].y, k = star[k].id;
        }
        star[i] = (atom) {
            k, x, y
        };
        h[i] = x;
    }
    sort(h + 1, h + Q + 1);
    tot = unique(h + 1, h + Q + 1) - h - 1;
    rep(q, 1, Q) {
        int x = lower_bound(h + 1, h + tot + 1, star[q].x) - h, y = star[q].y, k = star[q].id;
        if (!k)
            ice.add(x, y);
        else
            fire.add(x, y), suf[x] += y;
        int p = 0, all = fire.ask(tot), sum = 0;
        for (int i = 21; i >= 0; i--) {
            int u = (p | (1 << i));
            if (u <= tot && sum + ice.C[u] + fire.C[u] <= all + suf[u])
                p = u, sum += ice.C[u] + fire.C[u];
        }
        int w1 = calc(p), w2 = calc(p + 1);
        if (w1 <= 0 && w2 <= 0)
            puts("Peace");
        else if (w1 > w2)
            printf("%d %d\n", h[p], w1 * 2);
        else {
            p = 0, sum = 0;
            for (int i = 21; i >= 0; i--) {
                int u = (p | (1 << i));

                if (u <= tot && all - sum - fire.C[u] + suf[u] >= w2)
                    p = u, sum += fire.C[u];
            }
            printf("%d %d\n", h[p], w2 * 2);
        }
    }
    return 0;
}//这个常数也巨大无比。。

标签:板刷,ll,省选,int,2021,ans,include,color,define
来源: https://www.cnblogs.com/dixiao/p/14576792.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有