CSP-J模拟赛三补题报告

日期: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,求最小方差*n^{2}的值(确定根)

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值