日期:2023-10-01
学号:S12418
一:
总分数:40
T1【数字对应(digit)】:40
T2【技能学习(skill)】:0
T3【等于(equal)】:0
T4【最小方差(variance)】:0
二、比赛过程
算了我也不想多说什么
第一道题,自信模拟,样例也全过,但是我写的代码数据多了就会异常输出,含泪40pts
第二道题,一开始不会,后来写的是开局特判,然后均分,最后一分没有,卒。
第三道题,想了很久,计算过数据不会爆,结果时间超限(可能),卒。
第四道题,动规,不会,卒
三、比赛分析
T1:
1、题目大意
给定一个长度为n的序列,要求输出序列字典序最小,并且新序列元素不能存在于原序列,且需一一对应。
2、比赛中的思考
傻子都能想出来这题是贪心
从高位向下遍历,越高位对应的数字越小。我是建立了结构体的“字典”关系。
最后可能是数组炸了(但我开的是long long 阿?)
3、解题思路
同上,贪心,使用STL的map建立映射
当然你把map定义int 类型是可以的(答案就是这样)
4、AC代码
#include<iostream>
#include<map>
using namespace std;
const int N = 100010;
map<int,int>mp,mmp;
int n,x;
int a[N];
int pos = 1;
int main(){
cin>>n;
for(int i = 1;i<=n;i++){
cin>>a[i];
mp[a[i]]++;
}
for(int i = 1;i<=n;i++){
if(mmp[a[i]]){
cout<<mmp[a[i]]<<" ";
}
else{
while(mp[pos]){
pos++;
}
mmp[a[i]] = pos;
mp[pos] = 1;
cout<<mmp[a[i]]<<" ";
}
}
return 0;
}
T2【技能学习(skill)】:
1、题目大意
有一个大小为n的数组,m个“1”,此数组有以下特性:
当n[i]中“1”的数量超过k时,n[i]每分钟将会把自身(的值)复制并输出(也就是增加到答案里)
n[i]的“输出”是有上限的,上限为Q,等于或超过此上限对答案一点影响没有(也就是说没法继续复制输出了
特别地,“1”只能在第0个单位时间分配完毕且无法更改(可以有剩余)
求经过t个单位时间后,所有n[i]的值之和最大是多少。
2、比赛中的思考
如果“1”的数量过少,那就只给一个n[i];如果“1”的数量很多,那么就均分(以获得更高的产能效率)
可能是我的方法不对,最终也没拿到分数。
3、解题思路
和我的思路差不多。
需要注意的是,在最后求解过程中,我们要选出“剩余最少”的那个方案。
4、AC代码
#include<iostream>
#include<cstdio>
using namespace std;
long long m,k,q,t;
long long ans = 0,more1,more2;
int n1,n2,n;
int main(){
cin>>n>>m>>k>>q>>t;
if(n*k>m){
n = (int)(m/k);
}
m-=n*k;
if(m%n){
more1 = k+m/n+1;
n1 = (int)(m%n);
}
more2 = k+m/n;
n2 = n-n1;
ans = min(more1*t,q)*n1+min(more2*t,q)*n2;
cout<<ans<<endl;
return 0;
}
T3【等于(equal)】:
1、题目大意
给定一个长度为 n 的序列,并且序列中每个元素属于 -2,-1,1,2
中的一个。
请问多少个 子数组 满足最大值的绝对值等于最小值的绝对值。
(补题者附注:子数组必须连续)
2、比赛中的思考
从1~n枚举所有区间,然后计算绝对值(不出意外时间超了,但不知道为什么没得分)
3、解题思路
首先,找出元素完全一致的区间(比如说1,-1,2222等)
然后如果区间长度比较大,计算这些数能搞出多少个区间(公式:总区间数 = 元素个数*(元素个数+1)/ 2)
再然后就是找-2~2,此区间中间可以添加任何东西
最后就是找-1~1,此区间不能有绝对值 = 2的元素
输出总区间个数即可
4、AC代码
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
const int maxn = 5e5+10;
const int inf = 0x3f3f3f3f;
int n;
ll ans,num[maxn];
int nxt[maxn][5];
ll startpos,endpos;
int main(){
cin>>n;
memset(nxt,0x3f,sizeof nxt);
for(int i = 1;i<=n;++i){
cin>>num[i];
}
ll cnt = 1,lst = num[1];
for(int i = 2;i<=n;i++){//找完全一致的区间长度
if(num[i] == lst){
++cnt;//相同就计数
}
else{//不同就计算之前cnt个相同的数能搞出几个子集合
ans+=cnt*(cnt+1)/2;
cnt = 1;
lst = num[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][num[i]+2] = i;
int maxpos1 = nxt[i][1+2];//i后面最近的1的位置
int maxpos2 = nxt[i][2+2];//i后面最近的2的位置
int minpos1 = nxt[i][-1+2];//i后面最近的-1的位置
int minpos2 = nxt[i][-2+2];//i后面最近的-2的位置
startpos = max(maxpos2,minpos2);
//-2和2之间可以加任何东西右端同样
endpos = n+1;//从右往左算就行了
if(startpos!=inf&&startpos<endpos){//前提:找到2和-2之间的一个闭区间
ans+=endpos-startpos;//这个算一个,同时与右侧形成endpos- startpos-1个闭区间就行了
}
startpos = max(maxpos1,minpos1);//找1和-1形成的闭区间右端点
endpos = min(min(maxpos2,minpos2),n+1);//保证区间内不能有-2或2,并且向右拓展时也不能有
if(startpos!=inf&&startpos<endpos){//有1和-1,没有2和-2
ans+=endpos - startpos;//同上
}
}
cout<<ans<<'\n';
return 0;
}
T4:
1、题目大意
一棵无根树T,节点数n,边数n-1,求最小方差*的值(确定根)
2、比赛中的思考
可以说是毫无思路(我根本没学过dp)
我试着从链树入手,但也没成功,卒
3、解题思路
模板题,树型dp。
实现用深搜。
链表存储各组边。
最后确定状态转移方程,完毕。
(我能写出来dfs但是想不出状态转移方程)
总结:这一次考得不是一般的烂,不该丢的分比拿到的还多
4、AC代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<climits>
using namespace std;
#define ll long long
const int N = 1e5+5;
int t,head[N],vis[N],nex[N],cnt,v[N];
ll n,size[N],sum1[N],sum2[N],ans;
void add(int x,int y){
v[++cnt] = y;
nex[cnt] = head[x];
head[x] = cnt;
}
void dfs(int x){
vis[x] = 1;//标记
//如果已知了孩子的num1,那么转移到其父亲的sum1中时,所有孩子的孩子的距离+1
for(int i = head[x];~i;i=nex[i]){//遍历x的孩子
int y = v[i];//y是x的孩子
if(vis[y]) continue;
dfs(y);//继续深搜y
size[x]+=size[y];//x为根的树的节点个数+其孩子v为根的节点个数
sum1[x]+=sum1[y];//距离和转移
sum2[x]+=sum2[y];//距离平方和转移
}
sum2[x]+=size[x]+2*sum1[x];
//以下两行顺序不能颠倒:31行用的sum1是没有额外距离的
sum1[x]+=size[x];//距离和中,所有紫薯转移时,每个节点到根x的距离应+1
size[x]+=1;//以x为根的树结点个数+1,算上了x本身
}
void dfss(int x,ll s1,ll s2){
vis[x] = 1;
//s1是这个子树其父亲那部分距离和的贡献
//s2是这个子树其父亲那部分距离平方和的贡献
//以x为根的公式 序列方差*n^2公式:n*距离平方和-距离和的平方
ans = min(ans,n*(s2+sum2[x])-(sum1[x]+s1)*(sum1[x]+s1));
for(int i = head[x];~i;i = nex[i]){
int y = v[i];
if(vis[y]) continue;
//其父节点那一半贡献,总量-v子树的贡献+父亲的父亲的贡献
ll ans1 = sum1[x]-(sum1[y]+size[y])+s1;//和
ll ans2 = sum2[x]-(sum2[y]+2*sum1[y]+size[y])+s2;//平方和
ll sz = n-size[y];//其父节点另一边的子树节点个数
//y作为孩子,x作为父亲
//其父亲给其和贡献 s1+每个点距离+1
//其父亲给其平方和贡献 父亲那边子树的平方和+2倍的和+节点个数
dfss(y,ans1+sz,ans2+2*ans1+sz);
}
}
int main(){
cin>>t;
while(t--){
//初始化
memset(head,-1,sizeof head);
memset(vis,0,sizeof vis);
ans = LLONG_MAX;
cnt = 0;cin>>n;
for(int i = 1;i<=n;i++) sum1[i] = sum2[i] = size[i] = 0;
for(int i = 1;i<n;i++){
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1);
memset(vis,0,sizeof vis);
//s1是其父亲对应的子树(另一半)给他的距离和的贡献值
//同理,s2是其父亲对应的子树(另一半)给他的距离平方和的贡献值
dfss(1,0,0);
cout<<ans<<endl;
}
return 0;
}