1.比赛概况:
比赛共4题,110(240)/400,T1~T4:0/100/0/10(100/100/30/0)
气死我了,题目名称写错了,坑了我130分。n~ha~ha~a~~~~~
2.比赛过程:
T1是一道简单的用桶的题,但数据过大要用map.(花50min却死于文件名)
T2是一道数学题,我花了40min来推,终于写完了(这几天第一个AC)
T3以为是个DP(500000的DP?),就按DP来写,发现不行。就开始花式骗分,几乎骗了60%,但因为某些原因暴零了。
T4本以为很简单,没想到很难。只能打暴力+骗分。(因为时间不够还没调出来)
3.题解报告:
【T1:人员借调】
1. 情况: 0分,已补题(其实能AC)
2. 题意:有一个长度为n的序列A,构成一个长度为n的序列B。序列B中数字不能在序列A中出现过,并且序列A中第i个正整数与序列B中的第i个正整数一一对应。求字典序最小的序列B。
3. 数据范围:
对于20%数据:
对于40%数据:
对于60%数据:
对于另外的20%数据:1
对于100%数据:
4. 赛时本题做题想法/题解:用桶,但数据过大要用map。先吧每个数放桶里,再遍历桶,找n个里面没有的数。再根据A依次输出。
5. AC代码:
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
int n,a[100005],j=1;
map<int,int> mp,m;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(mp[a[i]]==0){
mp[a[i]]=1;
}
}
for(int i=1;i<=n;i++){
if(m[a[i]]==0){
while(mp[j]!=0) j++;
m[a[i]]=j++;
cout<<m[a[i]]<<" ";
}
else cout<<m[a[i]]<<" ";
}
return 0;
}
【T2:技能学习】
1. 情况: 100分
2. 题意:有n个同学有m份学习资料。学习资料可以随意分给每位同学。但是某位同学如果学习资料数量不足k份,将掌握不了新技能。某位同学拿到了p份学习资料,每分钟会增长p点技能点,最多到Q。总共有t分钟,求技能点之和最多是多少?注意:第0分钟将所有学习资料发放完(按照整份发放),不再调整。
3. 数据范围:
在 30%数据下:
在70%数据下:
在 100%数据下:
4. 赛时本题做题想法/题解:一道数学题,就要用数学来解决。(阳寿算法:O(1))
分类讨论:如果n位同学分m分资料不够分,就舍弃n-n/m位同学。剩下的同学分两类,一类有x人每人p份,另一类是n-x人p+1份。x=min(n,m%n),p=(m/n);
5. AC代码:
这样,加上一些压行,一份很有逼格的代码就出来了。(比正解还有逼格!)
#include<iostream>
#include<cstdio>
using namespace std;
long long n,m,k,q,t;
int main(){
cin>>n>>m>>k>>q>>t;
if(n*k>m) n=m/k;
if(k*t>=q) cout<<q*n;
else cout<<(n-min(n,m%n))*min((m/n)*t,q)+min(n,m%n)*min((m/n+1)*t,q);
return 0;
}
【T3:等于】
1. 情况: 0分,已补题。
2. 题意:一个长度为n的序列,序列中每个元素属于 -2,-1,1,2 中的一个。求多少个子数组满足最大值的绝对值等于最小值的绝对值。
3. 数据范围:
在 20%数据下:
另有10%数据:
另有10%数据:所有数字的绝对值相等,
另有10%数据:所有数字均为正整数,
另有20%数据:不存在数字 -2,
另有10%数据:
在100%数据下:
4. 赛时本题做题想法:以为是个DP(500000的DP?),就按DP来写,发现不行。就开始花式骗分,几乎骗了60%,但因为某些原因暴零了。(请欣赏花式骗分代码)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,a[500005],mx[500005][35],mn[500005][35],po[30],ans=0,p2=1,p3=1,p4=1;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(i!=1&&abs(a[i])!=abs(a[i-1])) p2=0;
if(a[i]<0) p3=0;
if(a[i]==-2) p4=0;
}
memset(mx,-0x3f,sizeof(mx));
memset(mn,0x3f,sizeof(mn));
for(int i=1;i<=n;i++){
mx[i][1]=mn[i][1]=a[i];
}
for(int i=1;i<30;i++) po[i]=(po[i-1]<<1);
for(int j=1;po[j]<=n;j++){
for(int i=1;i+j<=n;i++){
mx[i][j]=max(mx[i][j-1],mx[i+po[j-1]][j-1]);
mn[i][j]=min(mn[i][j-1],mn[i+po[j-1]][j-1]);
}
}
if(n<=10000){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
int k=log2(j-i+1);
if(abs(max(mx[i][k],mx[j-po[k]+1][k]))==abs(min(mn[i][k],mn[j-po[k]+1][k]))) ans++;
}
}
cout<<ans+n;
}
else if(p2) cout<<(1+n)*n/2;
else if(p3){
int i=1;
while(i<=n){
int p=1;
while(a[i]==a[i+1]){
i++;
p++;
}
i++;
ans+=(p+1)*p/2;
}
cout<<ans;
}
else if(p4){
int i=1;
while(i<=n){
int p=1;
while(a[i]==a[i+1]||a[i]==-a[i+1]){
i++;
p++;
}
i++;
ans+=(p+1)*p/2;
}
cout<<ans;
}
else{
cout<<(1+n)*n/4;
}
return 0;
}
5. 题解: 分类讨论。分为1.全一样的和2.全不一样的。全一样的用for+求和公式就行。不一样的又分(1).最大值1最小值 -1,和(2)最大值2,最小值-2 。考虑固定左端点时,如何找合法的右端点。对于(1)右端点需要保证:区间中有1和-1,区间中没有2和-2。用一个nxt数组来标记,nxt[i][j]表示i~n中出现j的最小的下标。对于(2)右端点需要保证:区间中有2和-2,同理。转移方程为
(加2是因为最小是-2)
6. AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[500005],nxt[500005][5];
long long ans=0,s,e;
int main(){
scanf("%d",&n);
memset(nxt,0x3f,sizeof(nxt));
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
long long p=a[1],cnt=1;
for(int i=2;i<=n;i++){
if(a[i]==p) cnt++;
else{
ans+=cnt*(cnt+1)/2;
cnt=1;
p=a[i];
}
}
ans+=cnt*(cnt+1)/2;
for(int i=n;i>=1;i--){
for(int j=0;j<=4;j++) nxt[i][j]=nxt[i+1][j];
nxt[i][a[i]+2]=i;
int mx1=nxt[i][3],mx2=nxt[i][4],mn1=nxt[i][1],mn2=nxt[i][0];
s=max(mx2,mn2);
e=n+1;
if(s!=0x3f3f3f3f&&s<e) ans+=e-s;
s=max(mx1,mn1);
e=min(min(mx2,mn2),n+1);
if(s!=0x3f3f3f3f&&s<e) ans+=e-s;
}
cout<<ans;
return 0;
}
【T4:最小方差】
1. 情况:10分,已补题
2. 题意:给定一颗无根树n个点n-1条边,边权=1 。在n个点中寻找一个树根 ,确定后计算出树上每个点到根的距离,得到一个长度为n的序列 。求序列最小的方差。
注意:输出的方差值乘以 。提示:设序列n的平均值为x,那么乘以
后的方差为
。(t组数据)
3. 数据范围:
对于10%数据:
另有20%数据:
另有20%数据:给定的数据为一条链
对于 100%数据:
4. 赛时本题做题想法:一看是个图论,立马精神了。一看题,又懵了。不甘心的打了个暴力,又开始骗分(可惜时间不够了,就40min了),代码还没调出来。
5. 题解:这题是个换根DP(这东西能出在J组?)设p为根,则说明我们需要维护各距离的平方和,以及各距离的和。状态转移时需要维护子树上的点数量。某一个节点为根时,方差的答案有两个来源:一个是该结点所在的子树的贡献,还有一个来源是当前点上方的祖先结点对的贡献。
6. AC代码:
这题已超出本人极限,只能贴个正解了。
#include <bits/stdc++.h>
using namespace std;
#define ll unsigned long long
const int maxn = 1e5 + 10;
//sum1表示以i为根的子树上的各点到i的距离和
//sum2表示以i为根的子树上的各点到i的距离平方和
//sz表示以i为根的子树的节点个数
ll sum2[maxn], sum1[maxn], sz[maxn], n, res;
vector<int> G[maxn];//存树的vector
void dfs1(int u, int f) {//u是当前搜的点,子树的根,f是其父节点
//如果已知了孩子的sum1,那么转移到其父亲的sum1中时,所有孩子的孩子的距离+1
for (int i=0;i<G[u].size();i++) {//遍历u的孩子
int v=G[u][i];
if (v == f) continue;//双向存储,如果当前遍历到其父亲则跳过
dfs1(v, u);//继续以v为起点深搜,u是其父亲
sz[u] += sz[v];//u为根的树的结点个数+其孩子v为根的结点个数
sum1[u] += sum1[v];//距离和转移
sum2[u] += sum2[v];//距离平方和转移
}
//公式转换 u是v的父亲,
//如果v的距离平方和是(x1)^2+(x2)^2+(x3)^2
//这些点到u的距离统一+1,则平方和变化(x1+1)^2+(x2+1)^2+(x3+1)^2
//拆:x1^2+2*x1+1 +x2^2+2*x2+1 +x3^2+2*x3+1
//即 x1^2+x2^2+x3^2 +2(x1+x2+x3) +3
//即 sum2[v] +2*sum1[v]+子节点个数
sum2[u] += sz[u] + 2 * sum1[u];//25行和26行顺序不能颠倒:因为25行中用的sum1是没有额外距离的sum1
sum1[u] += sz[u];//距离和中,所有子树转移时,每个结点到根u的距离应该+1
sz[u] += 1;//以u为根的树节点个数+1,算上了u本身
return;
}
void dfs2(int u, int f, ll s1, ll s2) {
//u作为根,f是其父亲
//s1是u这个子树对其父亲距离和的贡献
//s2是u这个子树对其父亲的距离平方和的贡献
//以u为根的公式 序列方差*n方公式:n*距离平方和-距离和的平方
res = min(res, n * (s2 + sum2[u]) - (sum1[u] + s1) * (sum1[u] + s1));
for (int i=0;i<G[u].size();i++) {//遍历u的孩子
int v=G[u][i];//遍历u的邻接点
if (v == f) continue;//v恰好是父亲,越过
//其父节点的那一半的贡献,总量减去v子树的贡献+父亲的父亲的贡献
ll ret1 = sum1[u] - (sum1[v] + sz[v]) + s1;//和版本
ll ret2 = sum2[u] - (sum2[v] + 2 * sum1[v] + sz[v]) + s2;//平方和版本
ll szu = n - sz[v];//其父节点那边的子树的节点个数
//v作为孩子,u作为父亲
//其父亲给其和贡献s1+每个点距离+1
//其父亲给其平方和贡献 父亲那边子树的平方和+2倍的和+节点个数
dfs2(v, u, ret1 + szu, ret2 + 2 * ret1 + szu);
}
return;
}
int main() {
int t;
cin >> t;
while (t--) {//t组数据
cin >> n;
for (int i = 1; i <= n; i++) {//先初始化,所有点清空
G[i].clear();
sum1[i] = sum2[i] = sz[i] = 0;
}
for (int i = 1; i <= n - 1; ++i) {//n-1条边
int u, v;
cin >> u >> v;//u和v之间有一条边
G[u].push_back(v);//u的邻接点加入一个v
G[v].push_back(u);//v的邻接点加入一个u
}
res = LONG_LONG_MAX;//res初始化极大值
//一开始认为1是初始根,其没有父亲
dfs1(1, 0);//1作为初始根,其父亲是0,进行第一轮深搜
//1作为初始根,其父亲是0,
//s1是其父亲对应的子树(另一半)给他的距离和的贡献值
//s2是其父亲对应的子树(另一半)给他的距离平方和的贡献值
dfs2(1, 0, 0, 0);
cout << res << endl;
}
}
4. 赛后总结:
今天的得分很可惜,丢了130分。以后要注意文件操作,(一个字母100分),合理安排时间。(实在离谱的题就弃了吧)。
(听说明天题很简单)
日期:2023年10月03日星期二
姓名:董峻熙