【题解】NOIP普及组2019

用了大概一个晚自习来做这四道题,顺便复习一下以前学过的知识。

A. 数字游戏

签到题。。。

#include <iostream>
using namespace std;
string s;
int ans;
int main() {
    
    
    cin >> s;
    for (int i = 0; i < s.size(); i++)
        if (s[i] == '1')
            ans++;
    cout << ans;
}

B.公交换乘

按题意模拟即可。

  1. 若type==0,累加花费,从优先队列中压入二元组(x,y),x表示花费,y表示时间。
  2. 若type==1,首先去掉超时的项。然后一直top,直到q.top().x>=x。将之前top的又push回去(不包括当前选择的项)。
  3. 输出ans。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;

struct node {
    
    
    int x, y;
    friend bool operator<(node a, node b) {
    
     return a.y > b.y; }
    node() {
    
    }
    node(int X, int Y) {
    
     x = X, y = Y; }
};

int n, ans, tot;
priority_queue<node> q;

int main() {
    
    
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
    
    
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        if (!x) {
    
    
            ans += y;
            q.push(node(y, z));
        } else {
    
    
            bool flag = 0;
            queue<node> p;
            while (q.size() && z - q.top().y > 45) q.pop();
            while (q.size()) {
    
    
                node t = q.top();
                q.pop();
                if (t.x >= y) {
    
    
                    flag = 1;
                    break;
                }
                p.push(t);
            }
            if (!flag)
                ans += y;
            while (p.size()) {
    
    
                q.push(p.front());
                p.pop();
            }
        }
    }
    printf("%d", ans);
}

C.纪念品

本题笔者并没有做出来。。。

分析:
正解是背包。
背包问题最重要的是弄清楚价值和背包容量。首先理解题意,假如要记录所有当前已买纪念品,显然是不可能的。

所以,当天买的纪念品,第二天必须全部卖掉。为什么呢?假设你在第i天买了t个物品j,价值是price[i][j],第二天则变成了price[i+1][j]。此时保留这t个纪念品,等价于先以price[i+1][j]卖掉,再以price[i+1][j]买回来,手中的钱不变。

结论:在第i天,拥有纪念品和拥有它的价值是一样的。

就好像炒股,只不过没有了买入卖出的手续费。

所以,背包就是第i天的钱数,买物品j的价值就是price[i+1][j]-price[i][j](这里的价值是买它所获得的利润),j的体积就是price[i][j].

d p [ k ] = m a x ( d p [ k ] , d p [ k − p r i c e [ i ] [ j ] ] + p r i c e [ i + 1 ] [ j ] − p r i c e [ i ] [ j ] ) dp[k]=max(dp[k],dp[k-price[i][j]]+price[i+1][j]-price[i][j]) dp[k]=max(dp[k],dp[kprice[i][j]]+price[i+1][j]price[i][j])

我们把每天都额外加上前一天的ans元钱能获得的最大利润,重复t天,就得到了答案。

#include<bits/stdc++.h> 
using namespace std;
const int N=105;
const int M=10005;


int t,n,m,cost[N][N],dp[M],ans;

int main() {
    
    
	scanf("%d%d%d",&t,&n,&m);
	for(int i=1;i<=t;i++)
	    for(int j=1;j<=n;j++) 
	        scanf("%d",&cost[i][j]);
	ans=m;
	for(int i=1;i<t;i++) {
    
    
		memset(dp,0,sizeof(dp));
		for(int j=1;j<=n;j++) {
    
    
			for(int k=cost[i][j];k<=ans;k++) {
    
    
				dp[k]=max(dp[k],dp[k-cost[i][j]]+cost[i+1][j]-cost[i][j]);
			}
		}
		ans+=dp[ans];
	}
	printf("%d",ans);
}

这道题也不是特别难(码量不大),但一定要弄明白每个量的含义,以及容量、价值和体积。

说白了就是忘了背包怎么做了

D.加工零件

感觉比上一道好想点。

分析:
本题的关键在于:你不能照搬L(L<=1e9),假如从(a,l)开始搜索,一直搜到(1,0),一定会超时。

一句话题意:问是否有从1到a的长度为L的路径。

自然而然想到最短路。因为路径可逆,所以选择1为起点。假设dis[a]>L,显然不可能;若dis[a]<=L,我们选择分析奇偶性。因为从a到a一定存在长度为2的路径。

然后拆点:

for(int i=1,x,y;i<=m;i++) {
    
    
    //奇偶性相同的连边
	scanf("%d%d",&x,&y);
	son[x].push_back(y+n);
	son[y+n].push_back(x);
	son[y].push_back(x+n);
	son[x+n].push_back(y);
}

注意光判断奇偶性还不行,必须当最短路<=L才行。假设有两个非常远的点,那么L太小是一定传递不过去的。

ac代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;

int n,m,q,dis[N];
vector<int> son[N];

void dijkstra() {
    
    
	memset(dis,0x3f,sizeof(dis));
	queue<int> q;
	q.push(1);
	dis[1]=0;
	while(q.size()) {
    
    
		int x=q.front();q.pop();
		for(int i=0;i<son[x].size();i++) {
    
    
			int y=son[x][i];
			if(dis[x]+1<dis[y]) {
    
    
				dis[y]=dis[x]+1;
				q.push(y);
			}
		}
	}
}


int main() {
    
    
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1,x,y;i<=m;i++) {
    
    
		scanf("%d%d",&x,&y);
		son[x].push_back(y+n);
		son[y+n].push_back(x);
		son[y].push_back(x+n);
		son[x+n].push_back(y);
	}
	
	dijkstra();
	
	for(int i=1;i<=q;i++) {
    
    
		int x,y;
		scanf("%d%d",&x,&y);
		//若y为奇数,则应该找x+n到1的路径长度
		if((y%2==0&&dis[x]<=y)||(y%2==1&&dis[x+n]<=y))
		    printf("Yes\n");
		else printf("No\n");
	}
}

猜你喜欢

转载自blog.csdn.net/cqbzlydd/article/details/108205356