YL 模拟赛总结 9

Posted on 2024-03-02 17:15  _XOFqwq  阅读(4)  评论(0编辑  收藏  举报

Problem


T1

我们考虑一种贪心策略:对于价格前 \(n-1\) 小的咖啡,我们求出一种最优方案使得按照此方案买完咖啡后钱数 \(\ge 20\) 且最接近 \(20\)

至于如何求出最优方案,进行一遍 01 背包即可。

#include<bits/stdc++.h>
using namespace std;

int n,k;
int a[1031],dp[1031];

int main(){
    //freopen("overdraft.in","r",stdin);
    //freopen("overdraft.out","w",stdout);
    ios::sync_with_stdio(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    if(k<=20){ cout<<k; return 0; }
    sort(a+1,a+n+1);
    for(int i=1;i<n;i++)
        for(int j=k-20;j>=a[i];j--)
            dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
    cout<<k-dp[k-20]-a[n];
    return 0;
}

T2

显然,题目要求的即为树的重心到每个店的距离之和。

考虑树形 dp。

\(s_x\) 为节点 \(x\) 的“向下”的子树大小,则有:

\[s_x=\sum_{i \in S} s_i \]

(其中 \(S\) 为节点 \(x\) 的子节点的集合)

再令 \(f_x\) 为节点 \(x\) “向上”的字数大小,则有:

\[f_x=\max_{i \in \{V-s_x-1\}} s_i \]

(其中 \(V\) 为所有节点的全集)

根据 \(f_x\) 的计算式,我们知道 \(f_x\) 是包含了 \(s_x\) 的。

因此对于所有节点,取 \(f\) 值最大的即为树的重心。

剩下的求距离仅需再次 dfs 一遍即可解决。

#include<bits/stdc++.h>
using namespace std;

int n,ans,C;
int g[100031],f[100031];
bool vis[100031];
vector<int> G[100031];

void dfs(int x){
	vis[x]=1;
	for(auto i:G[x]){
		if(!vis[i]){
			dfs(i);
			g[x]+=g[i]+1,f[x]=max(f[x],g[i]+1);
		}
	}
	f[x]=max(f[x],n-g[x]-1);
	vis[x]=0;
}

int main(){
	cin>>n;
	for(int i=1,a,b;i<n;i++){
		cin>>a>>b;
		G[a].push_back(b);
		G[b].push_back(a);
	}
	
	dfs(1);
	f[0]=1e9+31;
	for(int i=1;i<=n;i++)
		if(f[C]>f[i]) C=i;
	//cout<<C<<' ';

	memset(g,0,sizeof(g));
	dfs(C);
	for(int i=1;i<=n;i++) ans+=g[i];
	cout<<ans;
	return 0;
}

T3

考虑贪心地使用 \(k\) 张优惠券。

具体而言,维护三个优先队列,分别表示原价、优惠价、和差价。

然后模拟买咖啡的过程。若当前的最小优惠价 \(+\) 最小差价 \(<\) 最小原价,则使用一张优惠券。

否则,说明当前使用优惠券不比买原价更优,直接买原价即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,k,ans;
int p[20031],c[20031];
struct node{
    int val,id;
    friend bool operator < (node a,node b){
        return a.val>b.val;
    }
};
bool vis[20031];
priority_queue<node> a,b;
priority_queue<int,vector<int>,greater<int> > d;

signed main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) cin>>p[i]>>c[i],a.push({p[i],i}),b.push({c[i],i});
    for(int i=1;i<=k;i++) d.push(0);
    while(m>0&&ans<n){
        while(vis[a.top().id]) a.pop(); while(vis[b.top().id]) b.pop();
        if(d.top()+b.top().val<a.top().val){
            node t=b.top(); int x=t.val+d.top(); 
            if(m<x) break; m-=x,d.pop(),d.push(p[t.id]-c[t.id]),vis[t.id]=1; 
        }
        else{ node t=a.top(); int x=t.val; if(m<x) break; m-=x,vis[t.id]=1; } ans++;
    }
    cout<<ans;
    return 0;
}

T4

考虑进行线性 dp 求解。

我们令 \(dp_{i,j}\) 表示选择 \(i\) 个物品,总重量为 \(j\) 时的方案总数。

转移方程即为

\[dp_{i,j}=\sum^{k=1}_{j \div 2 \le k \le j,k \neq y} dp_{i-1,j-k} \]

这样直接计算,时间复杂度为 \(O(n^2)\) 无法接受。

考虑进行前缀和优化。

维护一个前缀和数组 \(sum_{i,j}\),首先 \(dp_{i,j}\) 至少是 \(sum_{i-1,j \div 2}\)

然后分类讨论即可:

  • \(j>n\),则为了保持决策区间长度为 \(n+1\)(包括当前的),则 \(dp_{i,j}\) 需要减去 \(sum_{i-1,j-n-1}\)

  • \(j \div 2 \le y \le j\),则 \(j-y \sim j\) 这个决策区间应该被去除,即 \(dp_{i,j}\) 需要减去 \(dp_{i-1,j-y}\)

#include<bits/stdc++.h>
using namespace std;

const int MOD=998244353;
int n,x,y,ans;
int dp[31][100031],sum[31][100031];

int main(){
    cin>>n>>x>>y;
    dp[0][0]=1;
    for(int i=0;i<=x;i++) sum[0][i]=1;
    for(int i=1;i<=20;i++){
        for(int j=1;j<=x;j++){
            dp[i][j]=sum[i-1][j/2];
            if(j>n) dp[i][j]=(dp[i][j]-sum[i-1][j-n-1]+MOD)%MOD;
            if(j-j/2<=y&&y<=j) dp[i][j]=(dp[i][j]-dp[i-1][j-y]+MOD)%MOD;
            sum[i][j]=(sum[i][j-1]+dp[i][j])%MOD; 
        }
    }
    for(int i=0;i<=20;i++) ans=(ans+dp[i][x])%MOD;
    cout<<ans;
    return 0;
}