浙江省第十七届程序大赛赛后笔记
第一题和第二题都是水题,一次过稍微讲讲思路
A
这题意思就是输入后不超过1/3的分数超过80分,照题目模拟即可
B
这题的意思是养兔子,每天放1个胡萝卜, n n n只兔子,每只重量 W i Wi Wi, k k k天以后每只兔子的重量是多少,第 i i i只兔子兔子拿到胡萝卜的概率是 W i ∑ i = 1 n W i \frac{Wi}{\sum_{i=1}^nWi} ∑i=1nWiWi所以模拟即可,注意每次每只兔子重量变化后它拿到下一个的概率不变
C
C题的意思是一个集合,重排列后下标相同但元素不同的最多个数
{ 1 , 2 , 3 , 4 , 4 } {\lbrace1,2,3,4,4\rbrace} {1,2,3,4,4}重排列后 { 4 , 4 , 1 , 2 , 3 } {\lbrace4,4,1,2,3\rbrace} {4,4,1,2,3}最多有 5 5 5个不同的元素
先写出解决函数,一开始的想法是排序后头和尾比较,这样贪心进行判断每个集合最大的不同个数
int deal(int num[N],int k){
int maxx=0;
sort(num,num+k);
int cot=0;
for(int j=0;j<k;j++){
if(num[j]!=num[k-1-j]) cot++;
}
return cot;
//cout<<cot<<endl;
}
但是很明显在上述给的例子中
{ 1 , 2 , 3 , 4 , 4 } {\lbrace1,2,3,4,4\rbrace} {1,2,3,4,4}
{ 4 , 4 , 3 , 2 , 1 } {\lbrace4,4,3,2,1\rbrace} {4,4,3,2,1}
很明显不是最优解
如果知道最终串的每一个数字出现次数,只需要拿出现最多的数字的出现次数 m a x x maxx maxx和串的总长度 s u m sum sum比较就能算出答案——即当 m a x x ∗ 2 > s u m maxx*2>sum maxx∗2>sum时,快乐值为 ( s u m − m a x x ) ∗ 2 (sum-maxx) * 2 (sum−maxx)∗2,否则为 s u m sum sum。
int nn[N];
int deal(int num[N],int k){
memset(nn, 0, N);
int maxx=0;
for(int i=0;i<k;i++){
int t=nn[num[i]]++;
maxx = t>maxx?t:maxx;
}
return maxx*2>k?(k-maxx)*2:k;
}
这样可以对某一个集合进行输出最大的值
现在来看输入,输入有两种形式 1 k q [ 1.. k ] n 1\ k\ q[1..k]^n 1 k q[1..k]n和 2 x y 2\ x\ y 2 x y即第一种形式输入的集合 S i = S x + S y S_i=S_x+Sy Si=Sx+Sy, { 1 , 2 , 3 } + { 4 , 5 , 6 } = { 1 , 2 , 3 , 4 , 5 , 6 } \lbrace1,2,3\rbrace+\lbrace4,5,6\rbrace=\lbrace1,2,3,4,5,6\rbrace {1,2,3}+{4,5,6}={1,2,3,4,5,6}那么我们可以知道到最后其实只有一个集合需要进行判断,所以我们可以忽略 2 x y 2\ x\ y 2 x y的输入形式,把每个 1 k q [ 1.. k ] n 1\ k\ q[1..k]^n 1 k q[1..k]n加入集合进行判断后即可
该思路代码
#include<cstdio>
#include<iostream>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<algorithm>
#include<string>
using namespace std;
#define ll long long
const ll N =1e8+1;
int nn[N];
int deal(int num[N],int k){
memset(nn, 0, N);
int maxx=0;
for(int i=0;i<k;i++){
int t=nn[num[i]]++;
maxx = t>maxx?t:maxx;
}
return maxx*2>k?(k-maxx)*2:k;
}
int num2[N];
int num[N];
int index[N];
int main(){
int t;
scanf("%d",&t);
int n,k,first;
for(int o=0;o<t;o++){
scanf("%d",&n);
if(n==1){
scanf("%d",&first);
if(first==1){
scanf("%d",&k);
for(int j=0;j<k;j++)scanf("%d",&num[j]);
printf("%d\n",deal(num,k));
}
}
else{
ll ind=0;
for(int i=0;i<n;i++){
scanf("%d",&first);
if(first==1){
int k;
scanf("%d",&k);
for(int j=0;j<k;j++){
scanf("%d",&num[ind]);
ind++;
}
}
}
printf("%d\n",deal(num,ind));
}
}
return 0;
}
可惜TLE了,我们想过使用分治法,然后进行相加,但是分治法在找了几个例子后明显行不通
百度后,该题需要使用拓扑排序进行优化
下面是来自csdn鸡尾酒的题解
最大值的定义:任意排列这个串,使得这个串尽量多的位置的数字与原来的数字不同,不同的位置的总数即为快乐值。
也就是这个题的任务就是算出最后一个串的每个数字的出现次数和总长度。但是我们会发现每次操作得到的串又有很多数字,在合并时会非常难计算。所以我的想法是先去记录每个操作得到的序列被用了多少次,再把这些序列的每个数字 * 次数就得到了最终串的每个数字出现次数。
然后我们考虑倒推的过程,因为我们是知道最后一个序列是由哪两个序列组成的,那么我们就可以知道这两个序列被用了一次,再从这两个序列往前递推。假设某个序列被用了 Q Q Q次,那么组成它的两个序列被用的次数都要$ += Q 。 于 是 我 们 很 容 易 想 到 拓 扑 排 序 , 假 设 。于是我们很容易想到拓扑排序,假设 。于是我们很容易想到拓扑排序,假设x,y 组 成 了 组成了 组成了a$,则从a分别向x,y连一条有向边。但是并不能直接拓扑,需要算出哪些序列是有用的,否则入度的计算会出现问题。比如5,8这两个序列组成了序列9,会从9向5,8连边。5,8又组成了序列10,会从10向5,8连边,10是最终序列。那么按照倒着拓扑的思想,会从10出发,然后将5,8的入度–,但是我们会发现,此时5,8的入度并不为0,所以不会被加入到队列中。所以说我们要先BFS一遍,找到所有由终点推得的点的一个局部图,此时每个点的入度才是我们想要的。
代码
#include<bits/stdc++.h>
using namespace std;
const int mx=1000100;
#define ll long long
vector<int> ve[mx],vec[mx];
unordered_map<int,ll>mp;
int in[mx];
ll op[mx];
ll cnt[mx];
bool vis[mx];
ll n;
#define tpyeinput ll //输入类型
inline char nc() {
static char buf[1000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(tpyeinput &sum) {
char ch=nc();
sum=0;
while(!(ch>='0'&&ch<='9'))
ch=nc();
while(ch>='0'&&ch<='9')
sum=(sum<<3)+(sum<<1)+(ch-48),ch=nc();
}
void bfs(){
queue<int>q;
q.push(n);
vis[n]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i:vec[x]){
in[i]++;
if(!vis[i])
q.push(i),vis[i]=1;
}
}
}
void tuopu(){
cnt[n]=1;
queue<int>q;
q.push(n);
while(!q.empty()){
int x=q.front();q.pop();
for(int i:vec[x]){
cnt[i]+=cnt[x];
in[i]--;
if(!in[i]) q.push(i);
}
}
}
int main(){
ios::sync_with_stdio(0);
ll t;
read(t);
while(t--){
read(n);
mp.clear();
for(ll flag,x,y,m,i=1;i<=n;i++){
in[i]=0;vis[i]=0;
cnt[i]=0;
vec[i].clear();
ve[i].clear();
read(flag);
op[i]=flag;
if(flag==1){
read(m);
while(m--){
read(x);
ve[i].push_back(x);
}
}else{
read(x);
read(y);
vec[i].push_back(x);
vec[i].push_back(y);
}
}
bfs();
tuopu();
ll sum=0,ma=0;
for(int i=1;i<=n;i++){
if(op[i]==1&&cnt[i]){
for(int x:ve[i]){
mp[x]+=cnt[i];
}
}
}
for(auto x:mp){
sum+=x.second;
if(x.second>ma) ma=x.second;
}
if(ma*2>=sum) cout<<(sum-ma)*2<<"\n";
else cout<<sum<<"\n";
}
return 0;
}
D
题目描述:欧拉游览树
如果我们把欧拉游览树所经过的结点顺序写成一个序列,那么拥有 N N N个节点的树会被写成一个2N-1序列,这个序列叫做欧拉序列。
欧拉序列就是先找左子树到底后饭后父节点再找右子树,综合使用队列和递归解决
但目前未解决,真在学习出找到的比较详细的题解