Codeforces Round #536 (Div. 2) A B C D E(dp)

本文详细解析了Codeforces比赛1106中的五道题目,包括LunarNewYear系列问题,涵盖了从简单的计数问题到复杂的动态规划与图遍历算法。提供了完整的代码实现,适合算法初学者和竞赛选手参考。
摘要由CSDN通过智能技术生成

这可能是我做 d i v 2 div2 div2以来做的最好的一次?它居然 u n r a t e d unrated unrated了!哼╭(╯^╰)╮不给力的服务器!关键时刻拓机!虽然这次的D比较水。但是是我一次过了四题QAQ…
比赛传送门:https://codeforces.com/contest/1106

A. Lunar New Year and Cross Counting

题解:数交叉 X X X个数, O ( N 2 ) O(N^2) O(N2)枚举中点即可。

代码

#include<bits/stdc++.h>

using namespace std;
string s[505];
int n;

int count1(int x,int y)
{
	if(s[x][y] != 'X') return 0;
	int dir[4][2] = {-1,-1,-1,1,1,-1,1,1};
	for(int i = 0; i < 4; ++i) {
		int tx = x + dir[i][0];
		int ty = y + dir[i][1];
		if(tx < 0 || tx >= n || ty < 0 || ty >= n)
			return 0;
		if(s[tx][ty] != 'X') return 0;
	}
	return 1;
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
	cin >> n;
	for(int i = 0; i < n; ++i) {
		cin >> s[i];
	}
	int ans = 0;
	for(int i = 0; i < n; ++i) {
		for(int j = 0; j < n; ++j) {
			ans += count1(i,j);
		}
	}
	cout << ans << endl;
    return 0;
}

B. Lunar New Year and Food Ordering

题解:按题意模拟,用一个优先队列维护当前最便宜并且 i n d e x index index最小的食物,如果当前顾客所需要的食物数量不够,那就取 p o p pop pop出来最便宜的食物,直到满足当前顾客,如果满足不了那就输出 0 0 0;如果足够当前顾客需要的,那就直接输出数量乘以花费即可。( P s : Ps: Ps:千万要注意会爆long long!!)

代码


#include<bits/stdc++.h>

using namespace std;
const int N = 1E5+10;
struct node{
    int t,d;
}cust[N];

struct food{
    int c,d,id;
    bool operator < (const food & u) const {
        if(d == u.d) {
            return id > u.id;
        }
        return d > u.d;
    }
};
priority_queue<food> pq;
int c[N],d[N];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
    int n,m;
    cin >> n >> m;
    for(int i = 0; i < n; ++i) {
        scanf("%d",&c[i]);
    }
    for(int i = 0; i < n; ++i) {
        scanf("%d",&d[i]);
    }
    for(int i = 0; i < n; ++i) {
        pq.push(food{c[i],d[i],i});
    }
    for(int i = 0; i < m; ++i) {
        scanf("%d%d",&cust[i].t,&cust[i].d);
        cust[i].t--;
    }
    long long tmp = 0;
    for(int i = 0; i < m; ++i) {
        tmp = 0;
        int t = cust[i].t;
        int num = cust[i].d;
        if(num > c[t]) {
            tmp += 1LL * d[t] * c[t];
            num -= c[t];
            c[t] = 0;
            if(pq.empty()) tmp = -1;
            while(!pq.empty()) {
                if(tmp == -1) break;
                food cur = pq.top(); pq.pop();
                cur.c = min(cur.c,c[cur.id]);
                if(cur.c >= num) {
                    tmp += 1LL * cur.d * num;
                    cur.c -= num;
                    c[cur.id] -= num;
                    if(cur.c != 0)
                        pq.push(cur);
                    break;
                }else{
                    tmp += 1LL * cur.c * cur.d;
                    num -= cur.c;
                    c[cur.id] = 0;
                }
                if(pq.empty()) tmp = -1;
            }
        }else{
            c[t] -= num;
            tmp += 1LL * d[t] * num;
        }
        if(tmp == -1) tmp = 0;
        cout << tmp << endl;
    }
    return 0;
}

C. Lunar New Year and Number Division

说实话这题当时纯靠莽…,题意是给偶数个数,然后分组,每组至少两个,最后求最小化 ∑ j = 1 m s j 2 \sum_{j = 1}^{m}{s_j}^2 j=1msj2 s j s_j sj为第 j j j组的数字的和,一共有 m m m组。既然要最小化和,那么就要最小化 s j 2 {s_j}^2 sj2,我当时就猜测,肯定每组两个数,让每组最小的话肯定是最小和最大配,第二小和第二大配。然后莽了一发就A了。

题解:我们可以考虑两个数, a , b a,b a,b,答案只能是 (1) ( a + b ) 2 (a + b)^2\tag 1 (a+b)2(1)四个数, a , b , c , d a,b,c,d a,b,c,d分一组 (2) ( a + b + c + d ) 2 = ( a + b ) 2 + ( c + d ) 2 + 2 ( a + b ) ( c + d ) (a + b + c + d)^2 = (a+b)^2+(c+d)^2+2(a+b)(c+d)\tag 2 (a+b+c+d)2=(a+b)2+(c+d)2+2(a+b)(c+d)(2)分两组 (3) ( a + b ) 2 + ( c + d ) 2 (a + b)^2+(c+d)^2 \tag 3 (a+b)2+(c+d)2(3)
对比 ( 2 ) ( 3 ) (2)(3) (2)(3)显然每组的数量越少,总和越小。我们再观察 ( 3 ) (3) (3),拆开:
(4) a 2 + b 2 + c 2 + d 2 + 2 ( a b + c d ) a^2+b^2+c^2+d^2+2(ab+cd)\tag 4 a2+b2+c2+d2+2(ab+cd)(4)
如果我们想要 ( 4 ) (4) (4)尽可能的小,那么我们就需要最小化 a b ab ab c d cd cd,由乘法的性质可得让 a b s ( a − b ) abs(a-b) abs(ab) a b s ( c − d ) abs(c-d) abs(cd)尽可能大即可。因此我们可以证明最小的和最大的配,第二小和第二大的配,以此类推的做法是正确的。

代码

#include<bits/stdc++.h>

using namespace std;
const int N = 3E5+10;
long long a[N];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
	int n;
	cin >> n;
	for(int i = 0; i < n; ++i) {
		scanf("%lld",&a[i]);
	}
	sort(a,a+n);
	long long sum = 0;
	for(int i = 0; i < n / 2; ++i) {
		sum += (a[i] + a[n - i - 1]) * (a[i] + a[n - i - 1]);
	}
	cout << sum << endl;
    return 0;
}

D. Lunar New Year and a Wander

题解:既然要访问的路径字典序最小,那么我们用优先队列+bfs就可以很好解决了。

代码

#include<bits/stdc++.h>
#define P pair<int,int>
using namespace std;
const int N = 1E5+10;
//vector<int> e[N];
priority_queue<int,vector<int>,greater<int> > e[N];
bool vis[N];
vector<int> ans;
priority_queue<int,vector<int>,greater<int> > pq;
void bfs()
{
    pq.push(1);
    vis[1] = 1;
    ans.push_back(1);
    while(!pq.empty()) {
        int cur = pq.top();
        if(vis[cur] == 0) {
            vis[cur] = 1;
            ans.push_back(cur);
        }
        pq.pop();
        for(; !e[cur].empty(); e[cur].pop()) {
            int v = e[cur].top();
            pq.push(v);
        }
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
    int n,m,x,y;
    cin >> n >> m;
    for(int i = 0; i < m; ++i) {
        scanf("%d%d",&x,&y);
        e[x].push(y);
        e[y].push(x);
    }
    bfs();
    int len = ans.size();
    for(int i = 0; i < len; ++i) {
        printf("%d%c",ans[i],i==len-1?'\n':' ');
    }
    return 0;
}

E. Lunar New Year and Red Envelopes

题意 B o b Bob Bob在每个 [ s i , t i ] [s_i,t_i] [si,ti]时间段内抢红包,只要在当前时间 x x x可以抢红包,那么他就一定会去抢。然后没抢完一个红包,那么他必须等到 d i + 1 d_i+1 di+1时间才能抢下一个红包。期间 A l i c e Alice Alice最多可以 m m m次阻挠 B o b Bob Bob 在时间 x x x时抢红包。问 B o b Bob Bob最少可以获得多少红包。

题解:我们可以将问题分为两个部分,没有 A l i c e Alice Alice阻挠, B o b Bob Bob所能获得的最少收益。和有 A l i c e Alice Alice不超过 m m m次时间点阻挠最少所能获得的收益。
那么第一部分是可以通过优先队列预处理出来的,同时我们记录每个时间点的收益,和下一次抢红包的时间点。然后对于不超过 m m m次的阻挠,我们可以用动态规划来解决。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示在 i i i时间时 j j j次阻挠最少所能获得的收益。我们发现状态的转移有 i − 1 i-1 i1 n x t [ i ] nxt[i] nxt[i]两种,并且每次抢完红包必须等到 d i + 1 d_i+1 di+1才能抢,也就意味着下一次抢红包时间是大于你的决策时的时间的,因此我们不妨考虑倒过来转移。(实际上正着来也是可以的,而且官方题解是用的滚动数组)就有 { d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ n x t [ i ] ] [ j ] + g o t [ i ] ) d p [ i ] [ [ j ] = m i n ( d p [ i ] [ j ] , d p [ i + 1 ] [ j − 1 ] ) \begin{cases}dp[i][j] =min(dp[i][j],dp[nxt[i]][j]+got[i])\\\\dp[i][[j] = min(dp[i][j],dp[i+1][j-1])\end{cases} dp[i][j]=min(dp[i][j],dp[nxt[i]][j]+got[i])dp[i][[j]=min(dp[i][j],dp[i+1][j1])
最后答案就是 m i n { d p [ 1 ] [ j ] } min\{dp[1][j]\} min{dp[1][j]}

代码

#include<bits/stdc++.h>
typedef long long LL;d
using namespace std;
const int N = 1E5+10;
struct node{
	int s,t,d,w;
	bool operator < (const node & u) const{
		if(w == u.w) return d < u.d;
		return w < u.w;
	}
};

bool cmp(const node & a, const node & b){ return a.s < b.s; }

priority_queue<node> pq;

vector<node> v;
LL dp[N][201], got[N], nxt[N];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("input.in","r",stdin);
#endif
	int n,m,k,s,t,d,w;
	cin >> n >> m >> k;
	for(int i = 0; i < k; ++i) {
		cin >> s >> t >> d >> w;
		v.push_back(node{s,t,d,w});
	}
	sort(v.begin(),v.end(),cmp);
	int cur = 0;
	//1  4			
	//6  4 + 7      
	//11 4 + 7 + 9 <!> 11 <!>12 --> 4 + 7 = 11
	for(int i = 1; i <= n; ++i) {
		while(cur < k && v[cur].s <= i) 
			pq.push(v[cur++]);
		while(!pq.empty() && pq.top().t < i) pq.pop();
		if(!pq.empty()){
			node t = pq.top(); 
			got[i] = t.w;
			nxt[i] = t.d + 1;
		}else{
			got[i] = 0;
			nxt[i] = i + 1;
		}
	}
	memset(dp,0x3f,sizeof dp);
	dp[n + 1][0] = 0;
	for(int i = n; i > 0; --i) {
		dp[i][0] = dp[nxt[i]][0] + got[i];
	}
	for(int i = n; i >= 1; --i) {
		for(int j = 0; j <= m; ++j) {
			dp[i][j] = min(dp[i][j], dp[nxt[i]][j] + got[i]);
			dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
		}
	}
	LL ans = *min_element(dp[1],dp[2]);
	cout << ans << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值