五一题解
一套简单题,T4 严格小于正赛难度。
简单题
考察
构造,贪心,分类讨论,简单思维。感觉可能正赛有 T2 难度,不过真的很简单。
这题做不出来你就等着爆炸吧。
题解
一种很好的思考方式是根据部分分的弱化版问题来启发正解。
- \(n=1\) 时,当且仅当 \(x=y\) 能构造出来。
- 如果 \(x\) 和 \(y\) 都是正数,此时 \(y\) 肯定就必须等于序列里的最小值,那么就考虑,我需要有一个和为 \(x\) 的子段,此时由于最小值是 \(y\),所以这个子段的和(也就是要求的 \(x\))一定大于 \(n \times y\),因此判断 \(x\) 是否大于 \(n \times y\) 即可。
- 如果 \(x\) 是正,\(y\) 是负,这个显然可以瞎做,比如一个数设为 \(x\),第二个设为 \(y\),后面全是 \(0\) 就构造出来了,所以一定有解。
- 如果 \(x,y\) 都是负的,同理都是正数的情况,判断 \(y\) 是否小于 \(n \times x\) 即可。
- 注意到题目保证 \(y \le x\),所以没有 \(y\) 是正,\(x\) 是负的情况。不用考虑。
#include<iostream>
using namespace std;
int main(){
int T;
cin>>T;
while(T--)
{
int n,x,y;
cin>>n>>x>>y;
if(!(n==1&&x!=y)&&(x*y<=0||(y*n<=x&&x>0)||(x*n>=y&&x<0)))cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
煎蛋题
考察
贪心。这题做不出来就做不出来吧。
这题好像有点难了对于 T2 来说,但是考虑结论很简单,没能放到后面的位置,想这题的过程对大部分贪心是具有启发性的。
题解
还是先考虑简化版问题,如果每次固定只能吃两个,这要怎么做,显然的,最小的配最大的吃,这样一定最优,可以交换证明,然后排序配对记录最大值和最小值就行了,如果这步都不会私信来问我。
接下来考虑,我们把吃一个的操作如何扩展到只能吃两个的情况,我们做一步转化,吃一个美味值为 \(x\) 的,就等于吃一个 \(x\) 和一个 \(0\)。那么我们枚举要吃多少次一颗,设当前枚举要吃 \(i\) 次一颗,就把序列里加入 \(i\) 个 \(0\),然后按照上面的方法去做。注意到我们并不需要确定哪几颗糖是吃一颗的,因为我们一定尽量要让更大的自己吃,这样是优的。复杂度 \(O(n^2 \log n)\),可以通过。
另外一种思考的方向,你先考虑暴力,那么就是直接枚举吃了几次一颗,这是显然的。然后肯定要让大的值自己吃,所以这题就做出来了。
#include<bits/stdc++.h>
using namespace std;
const long long N=5e3+5;
long long n,m,a[N],f[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
long long ans=LONG_LONG_MAX;// 初始化成极大值
for(int i=0;i<=n;i++){
for(int j=1;j<=n;j++) f[j]=a[j];
for(int j=n+1;j<=n+i;j++) f[j]=0;
sort(f+1,f+n+i+1);// 构造填完 0 的新数组
long long ma=-LONG_LONG_MAX,mi=-ma;
for(int j=1;j<=(n+i+1)/2;j++){
long long x=f[j]+(j==n+i-j+1?0:f[n+i-j+1]);// 计算配对值,底层逻辑就是最大配最小,这里为了方便写了个三目运算符,你的代码可以自己想想怎么实现最大配最小
ma=max(ma,x),mi=min(mi,x);
}
ans=min(ans,ma-mi);
}
cout<<ans;
return 0;
}
简单提
考察
基础线性 DP,分数性质。
评价
简单的不像题。。。最基础的 DP 题目。考试不可能这么简单。
题解
考虑 DP,定义 \(f_i\) 表示到位置 \(i\) 最少要花费多少时间。然后考虑转移:
- 战术换位:仔细看的话就会发现它等同于找到一个相同的元素并且两者之间没有不同的元素进行转移。进一步剖析会发现它就是说它只能从这个数上次出现的位置转移。所以我们只需要一个额外的
lst数组记录 \(1\)∼\(9\) 的上一次出现位置,初始化为 \(0\)。每次转移时发现如果这个值不为 \(0\) 则进行转移。 - 长传快攻:注意到要求 \(1 \le j \le a_i\),然后 \(a_i\) 最大是 \(9\),所以可以直接枚举转移,\(f_i=\frac{i-j}{a_j}+f_j\)。
复杂度是对的了,但是注意到本题很卡常,分数算来算去很麻烦也很慢,因为只有 \(a\) 会变成分母,于是我们把所有的分数的分母换成 \(1\)~\(9\) 的最小公倍数 \(2050\),然后就可以直接加了,最后把结果和 \(2050\) 通分即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;// 缩写 long long,后文 ll=long long
int n;
inline ll gcd(ll a,ll b){
while(b){
a%=b;swap(a,b);
}
return a;
}
ll LCM=2520;//1-9的LCM
int main(){
cin>>n;
vector<short> a(n+1);//用vector开动态大小防止一次性太大炸了(虽然我不习惯)
vector<ll> f(n+1,1e18);
vector<ll> lst(10,0);//记得初始化
for(int i=1;i<=n;i++)cin>>a[i];
lst[a[1]]=1;
f[1]=0;
for(int i=2;i<=n;i++){
ll minn;
minn=1e18;
if(lst[a[i]]){
minn=2520;minn=minn+f[lst[a[i]]];//战术换位
}
lst[a[i]]=i;
for(int j=1;j<=9&&i-j>=1;j++){
if(a[i-j]<j)continue;
ll tmp=1ll*j*LCM/a[i-j];
tmp+=f[i-j];
minn=min(minn,tmp);//长传快攻
}
f[i]=minn;
}
ll g=gcd(f[n],LCM);//约分
cout<<f[n]/g<<' '<<LCM/g;
return 0;
}
拓展
当 \(a\) 能到达 \(n\) 的量级,保证 \(a\) 的最小公倍数 \(\le 10^6\)。想一想怎么做,状态定义还是一样的,去分母的技巧还是一样的。战术换位的转移是一样的因为它不与 \(a\) 相关。考虑长传快攻,然后就可以线段树优化 DP,维护每个地方 \(j\) 的 \(i-j\) 乘上去分母的逆,并把这个 \(f_j\) 转化成分母为 \(a_j\) 的,直接上个线段树维护系数即可。
人类题
考察
树的重心,树论,贪心。
评价
稍微有水平一点的题目,但是放到压轴算简单啊,你大概率看题解会不了来着,不会来问我吧。
解法
考虑弱化版,我们如果要连两棵树,要怎么连使得不协调度最小。显然的,连接重心。证明:考虑重心的定义,使得每个子树大小都不超过一半,然后感性理解。接下来考虑怎么连,当我们把树都看成点,显然的,就是选一个点当中心点,把别的点都连到这个点上,拓展到树上也是一样的。最后,想一想怎么选定中心这个,考虑每条边 \((u,v,w)\) 对不协调度的贡献,显然就是 \(w \times sz_u \times sz_v\),\(sz_u\) 表示 \(u\) 那一边联通块的大小,这个式子也就是乘法原理。然后你发现 \(sz_u\) 和 \(sz_v\) 是有关系的,\(sz_u=n-sz_v\),所以贡献式子变成 \(w(sz_v)(n-sz_v)\),你分讨一下发现 \(sz_v\) 越小贡献越少,假设最大点数的树为 \(x\),另一树为 \(y\)。若选择 \(x\) 的重心作中心点,答案里就有一项 \(y\) 的贡献,若是选择 \(y\) 的重心作中心点,那么答案里就有一项 \(x\) 的贡献,其余都是相同项。我显然想要 \(y\) 的贡献因为它的贡献更小。所以得到策略,选择大小最大的一棵树的重心作中心点,然后把其余的都连在这上面。时间复杂度 \(O(n \log n)\),可以过。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MOD = 1000000007;
struct edge { int v, w; };
int n, m, k, a[1000010];
vector<edge> e[1000010];
//STEP1: 求所有树的重心(树中其他点到这个点距离和最小)
int p, mnval, vis[1000010], sz[1000010], f[1000010], g[1000010];
void dfs1(int fa, int x, int u) {
sz[x] = 1, f[x] = 0, vis[x] = u;
for (edge t: e[x]) {
int v = t.v, w = t.w;
if (v == fa) continue;
dfs1(x, v, u);
sz[x] += sz[v];
f[x] += f[v] + w * sz[v];
}
}
void dfs2(int fa, int x, int u) {
if (g[x] < mnval) mnval = g[x], p = x;
for (edge t: e[x]) {
int v = t.v, w = t.w;
if (v == fa) continue;
g[v] = g[x] + w * (sz[u] - 2 * sz[v]);
dfs2(x, v, u);
}
}
//STEP2: 按sz排序所有重心
int b[1000010], btp = 0;
bool cmp(int x, int y) { return sz[x] > sz[y]; }
//STEP3: 构造新树后求答案,每条边贡献为:边权*连接的两棵树点数之积
int ans = 0, nsz[1000010];
void dfs3(int fa, int x) {
nsz[x] = 1;
for (edge t: e[x]) {
int v = t.v, w = t.w;
if (v == fa) continue;
dfs3(x, v);
ans = (ans + (n - nsz[v]) * nsz[v] * w) % MOD;
nsz[x] += nsz[v];
}
}
signed main() {
cin >> n >> m;
k = n - m - 1;
for (int i = 1, x, y, z; i <= m; i++) {
cin >> x >> y >> z;
e[x].push_back({y,z});
e[y].push_back({x,z});
}
for (int i = 1; i <= k; i++) cin >> a[i];
sort(a + 1, a + k + 1);
//求所有树重心
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
dfs1(0, i, i);
mnval = 1e18, g[i] = f[i];
dfs2(0, i, i);
sz[p] = sz[i];
b[++btp] = p;
}
sort(b + 1, b + btp + 1, cmp);
//从点最多的树重心出发,连所有其他树的重心(点多的树优先用边权小的连)
int o = b[1];
for (int i = 2, j = 1; i <= btp; i++, j++) {
e[o].push_back({b[i],a[j]});
e[b[i]].push_back({o,a[j]});
}
dfs3(0, o);
cout << ans;
return 0;
}
/*
a<b,SZx>SZy>SZz,Sy>Sx>Sz
SZy*Sz+SZz*Sy+(a+b)*SZz*SZy+Sx*SZz+Sz*SZx+b*SZz*SZx+Sx*SZy+Sy*SZx+a*SZy*SZx
SZy*Sz+SZz*Sy+b*SZz*SZy+Sx*SZz+Sz*SZx+(a+b)*SZz*SZx+Sx*SZy+Sy*SZx+a*SZy*SZx
SZy*Sz+SZz*Sy+b*SZz*SZy+Sx*SZz+Sz*SZx+a*SZz*SZx+Sx*SZy+Sy*SZx+(a+b)*SZy*SZx
a*SZz*SZy+b*SZz*SZx
b*SZz*SZx+a*SZz*SZx
a*SZz*SZx+b*SZy*SZx
SZy
SZx
*/

浙公网安备 33010602011771号