2020-1-18
第一题
统计数字
题目描述
某次科研调查时得到了nnn个自然数,每个数均不超过1500000000(1.5×109)1500000000(1.5 imes 10^9)1500000000(1.5×109)。已知不相同的数不超过100001000010000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。
输入格式
共n+1n+1n+1行。
第一行是整数nnn,表示自然数的个数;
第222至n+1n+1n+1每行一个自然数。
输出格式
共mmm行(mmm为nnn个自然数中不相同数的个数),按照自然数从小到大的顺序输出。
每行输出222个整数,分别是自然数和该数出现的次数,其间用一个空格隔开。
输入输出样例
8 2 4 2 4 5 100 2 100
2 3 4 2 5 1 100 2
说明/提示
40%40\%40%的数据满足:1≤n≤10001 le n le 10001≤n≤1000
80%80\%80%的数据满足:1≤n≤500001 le n le 500001≤n≤50000
100%100\%100%的数据满足:1≤n≤2000001 le n le 2000001≤n≤200000,每个数均不超过1500000000(1.5×109)1500 000 000(1.5 imes 109)1500000000(1.5×109)
NOIP 2007 提高第一题
一些经验(教训): 第一题又特么打崩了?!
好吧 其实是我手贱写了两种算法 把STL那种没学过不知道问题的交上去了......
错误CODE
#include<bits/stdc++.h> using namespace std; const int MAXN=1e5+100; long long read(){ long long ans=0,f=1; char ch=getchar(); while(!isdigit(ch)) f*=(ch=='-')?-1:1,ch=getchar(); do ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar(); while(isdigit(ch)); return ans*f; } map<long long,int>h; int main(){ //freopen("count.in","r",stdin); //freopen("count.out","w",stdout); long long n=read(); for(int i=1;i<=n;i++){ long long x=read(); h[x]++; } for(int i=1;i<=h.size();i++){ if(h[i])cout<<i<<" "<<h[i]<<endl; } return 0; }
当我发现这道题可以直接暴力模拟AC时 .......
#include<bits/stdc++.h> using namespace std; #define re register #define ll long long #define get getchar() #define in inline in int read() { int t=0; char ch=get; while(ch<'0' || ch>'9') ch=get; while(ch<='9' && ch>='0') t=t*10+ch-'0',ch=get; return t; } struct num{ int id,sum; }a[200010]; //每个数出现的次数及它的值 int tot,n,h[200010]; int main() { n=read(),a[0].id=-199; for(re int i=1;i<=n;i++) h[i]=read(); sort( h+1,h+n+1); for(re int i=1;i<=n;i++) { if(a[tot].id==h[i])a[tot].sum++; else a[++tot].id=h[i],a[tot].sum=1; } for(re int i=1;i<=tot;i++) cout<<a[i].id<<' '<<a[i].sum<<endl; return 0; }
第二题
字符串的展开
题目描述
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d-h
”或者“4-8
”的字串,我们就把它当作一种简写,输出时,用连续递增的字母或数字串替代其中的减号,即,将上面两个子串分别输出为“defgh
”和“45678
"。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
(1) 遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号“-
”,减号两侧同为小写字母或同为数字,且按照ASCII
码的顺序,减号右边的字符严格大于左边的字符。
(2) 参数p1p_1p1:展开方式。p1=1p_1=1p1=1时,对于字母子串,填充小写字母;p1=2p_1=2p1=2时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3p_1=3p1=3时,不论是字母子串还是数字字串,都用与要填充的字母个数相同的星号“*”来填充。
(3) 参数p2p_2p2:填充字符的重复个数。p2=kp_2=kp2=k表示同一个字符要连续填充k个。例如,当p2=3p_2=3p2=3时,子串“d-h
”应扩展为“deeefffgggh
”。减号两边的字符不变。
(4) 参数p3p_3p3:是否改为逆序:p3=1p3=1p3=1表示维持原来顺序,p3=2p_3=2p3=2表示采用逆序输出,注意这时候仍然不包括减号两端的字符。例如当p1=1p_1=1p1=1、p2=2p_2=2p2=2、p3=2p_3=2p3=2时,子串“d-h
”应扩展为“dggffeeh
”。
(5) 如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:“d-e
”应输出为“de
”,“3-4
”应输出为“34
”。如果减号右边的字符按照ASCII
码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:“d-d
”应输出为“d-d
”,“3-1
”应输出为“3-1
”。
输入格式
共两行。
第111行为用空格隔开的333个正整数,依次表示参数p1,p2,p3p_1,p_2,p_3p1,p2,p3。
第222行为一行字符串,仅由数字、小写字母和减号“−-−”组成。行首和行末均无空格。
输出格式
共一行,为展开后的字符串。
输入输出样例
1 2 1 abcs-w1234-9s-4zz
abcsttuuvvw1234556677889s-4zz
2 3 2 a-d-d
aCCCBBBd-d
说明/提示
40%40\%40%的数据满足:字符串长度不超过555
100%100\%100%的数据满足:1≤p1≤3,1≤p2≤8,1≤p3≤21 le p_1 le 3,1 le p_2 le 8,1 le p_3 le 21≤p1≤3,1≤p2≤8,1≤p3≤2。字符串长度不超过100100100
NOIP 2007 提高第二题
考场:思路简单 照着题目要求if模拟但是代码冗杂并且不好揪错
但是经过我不懈的努力还是用3KB的量写出来啦! (我在嚣张什么)
#include<bits/stdc++.h> using namespace std; int p1,p2,p3,len,idx; int w[105]; char a[105]; void zhan(){ for(int i=1; i<=idx; i++) { if(i==idx&&w[i]==w[i-1]+1) break; if(w[i]+1==w[i+1]){ //cout<<endl; //cout<<w[i-1]+2<<"->"<<w[i+1]+1<<endl; for(int j=w[i-1]+2;j<=w[i+1]+1;j++) cout<<a[j]; continue; } if(int(a[w[i]-1])>=int(a[w[i]+1])||(int(a[w[i]-1])>=48&&int(a[w[i]-1])<=57&&int(a[w[i]+1])>57)) { for(int j=w[i-1]+2; j<=w[i]+1; j++) cout<<a[j]; continue; } if(int(a[w[i]-1])+1==int(a[w[i]+1])) { for(int j=w[i-1]+2; j<=w[i]+1; j++) { if(a[j]!='-') cout<<a[j]; } continue; } if(int(a[w[i]-1])>=48&&int(a[w[i]-1])<=57) { if(p1==3) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=a[w[i]-1]-'0'+1; j<=a[w[i]+1]-'0'-1; j++) { for(int k=1; k<=p2; k++) cout<<'*'; } cout<<a[w[i]+1]; } else { if(p3==1) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=a[w[i]-1]-'0'+1; j<=a[w[i]+1]-'0'-1; j++) { for(int k=1; k<=p2; k++) cout<<j; } cout<<a[w[i]+1]; } if(p3==2) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=a[w[i]+1]-'0'-1; j>=a[w[i]-1]-'0'+1; j--) { for(int k=1; k<=p2; k++) cout<<j; } cout<<a[w[i]+1]; } } } if(int(a[w[i]-1])>=97&&int(a[w[i]-1])<=122) { if(p1==3) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=int(a[w[i]-1])+1; j<=int(a[w[i]+1])-1; j++) { for(int k=1; k<=p2; k++) cout<<'*'; } cout<<a[w[i]+1]; } else { if(p1==1) { if(p3==1) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=int(a[w[i]-1])+1; j<=int(a[w[i]+1])-1; j++) { for(int k=1; k<=p2; k++) cout<<char(j); } cout<<a[w[i]+1]; } if(p3==2) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=int(a[w[i]+1])-1; j>=int(a[w[i]-1])+1; j--) { for(int k=1; k<=p2; k++) cout<<char(j); } cout<<a[w[i]+1]; } } if(p1==2) { if(p3==1) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=int(a[w[i]-1])+1; j<=int(a[w[i]+1])-1; j++) { for(int k=1; k<=p2; k++) cout<<char(j-32); } cout<<a[w[i]+1]; } if(p3==2) { for(int j=w[i-1]+2; j<=w[i]-1; j++) cout<<a[j]; //cout<<a[w[i]-1]; for(int j=int(a[w[i]+1])-1; j>=int(a[w[i]-1])+1; j--) { for(int k=1; k<=p2; k++) cout<<char(j-32); } cout<<a[w[i]+1]; } } } continue; } } for(int i=w[idx]+2;i<len;i++) cout<<a[i]; } int main() { //freopen("expand.in","r",stdin); //freopen("expand.out","w",stdout); cin>>p1>>p2>>p3; cin>>a; len=strlen(a); //cout<<len<<endl; //cout<<len; for(int i=0; i<len; i++) { //cout<<a[i]; if(a[i]=='-'&&a[i-1]=='-'&&i-1==0) continue; if(a[i]=='-'&&i!=0&&i!=len-1) w[++idx]=i; } w[0]=-2; //for(int i=1; i<=idx; i++) // cout<<w[i]<<" "; // cout<<endl; zhan(); return 0; }
下来后 和神犇PHDHD交流后发现人家代码长度后......
思路:函数调用判断减少for循环啊 if判断的数量(哎我真的蠢)
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(isdigit(ch)); return ans * f; } inline bool isValid(char a,char b){ if(a >= b) return false; if(isdigit(a) && isdigit(b)) return true; if((a >= 'a' && a <= 'z') && (b >= 'a' && b <= 'z')) return true; return false; } int main(){ // freopen("expand.in", "r", stdin); // freopen("expand.out", "w", stdout); int p1 = read(), p2 = read(), p3 = read(); string s; cin >> s; int len = s.length(); for(int i=0; i<len; i++){ if(s[i] == '-' && i != 0 && i != len-1 && isValid(s[i-1], s[i+1])){ int add = (!isdigit(s[i-1]) && (p1 == 2)) * ('A' - 'a'); char begin = s[i-1], end = s[i+1]; if(p3 == 2) swap(begin, end); int d = (begin < end) ? 1 : -1; for(char k=begin; k!=end; k+=d){ if(k == begin) continue; for(int j=1; j<=p2; j++){ if(p1 == 3) putchar('*'); else putchar(k+add); } } } else putchar(s[i]); } return 0; }
第三题 矩阵取数字游戏
题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的n×mn imes mn×m的矩阵,矩阵中的每个元素ai,ja_{i,j}ai,j均为非负整数。游戏规则如下:
- 每次取数时须从每行各取走一个元素,共nnn个。经过mmm次后取完矩阵内所有元素;
- 每次取走的各个元素只能是该元素所在行的行首或行尾;
- 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值×2i imes 2^i×2i,其中iii表示第iii次取数(从111开始编号);
- 游戏结束总得分为mmm次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入格式
输入文件包括n+1n+1n+1行:
第111行为两个用空格隔开的整数nnn和mmm。
第2∽n+12acksim n+12∽n+1行为n×mn imes mn×m矩阵,其中每行有mmm个用单个空格隔开的非负整数。
输出格式
输出文件仅包含111行,为一个整数,即输入矩阵取数后的最大得分。
输入输出样例
2 3 1 2 3 3 4 2
82
说明/提示
NOIP 2007 提高第三题
数据范围:
60%的数据满足:1≤n,m≤301le n, m le 301≤n,m≤30,答案不超过101610^{16}1016
100%的数据满足:1≤n,m≤801le n, m le 801≤n,m≤80,0≤ai,j≤10000 le a_{i,j} le 10000≤ai,j≤1000
思路
当我简单的看完题面后发现是DP 但是一直把每个数字隔离分析没有想到用区间DP来处理
最后还是用了一手爆搜狂砍20分
正解 懒人解
1.
- 求nn行最大得分和,每一行取数又不会影响到其他行,那么只要确保每一行得分最大,管好自家孩子就行了。(这个在动规中叫最优子结构)
- 每次取数是在边缘取,那么每次取数完剩下来的元素一定是在一个完整的一个区间中,又是求最优解,区间DP应运而生
2.
- 思路其实大同小异:按行进行区间DP,我们可以设区间[L,R]的最大值为f[L][R],用一个数组p[n]来储存2^n的值,采用记忆化搜索的办法,
- 设k=m-(R-L),可以得到状态转移方程:f[L][R]=max(num[L]*p[k]+dp(L+1,R),dp(L,R-1)+num[R]*p[k]),
西卡西!!
结果中数据竟然有超过2^80 妥妥爆long long (unsigned long long照样爆)
我们要使用高精度处理每一位数据(这才是这道题最难写的地方啊)
当然如果你跟本蒟蒻一样懒而且觉得自己在赛场上能写出来高精度
完全可以用int128水过去
#include<bits/stdc++.h> #define in(x) x=read() #define MAXN 81 #define k m-(R-L) #define bll __int128 using namespace std; inline int read() { int X=0,w=1; char ch=getchar(); while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();} while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar(); return X*w; } int n,m; int num[MAXN]; bll ans,p[MAXN],f[MAXN][MAXN]; bll dp(int L,int R)//记忆化搜索 { if(f[L][R]!=-1) return f[L][R]; if(R-L>=1) f[L][R]=max(num[L]*p[k]+dp(L+1,R),dp(L,R-1)+num[R]*p[k]); else f[L][R]=num[L]*p[k]; return f[L][R]; } void print(bll x) { if(!x) return; if(x) print(x/10); putchar(x%10+'0'); } int main() { in(n);in(m); p[0]=1; for(int i=1;i<=m;i++) p[i]=p[i-1]*2; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) in(num[j]); memset(f,-1,sizeof(f)); ans+=dp(1,m); } if(!ans) printf("0"); else print(ans); return 0; }
本着认真负责的态度 我将隔壁lrh老哥的高精度贴上去 让大家知道不努力学习就会被拉开的恐怖(偷懒的好处)
#include <bits/stdc++.h> using namespace std; const int N=85,mod=10000; int n,m; int ar[N]; struct node{ int p[505], len; node() { memset(p, 0, sizeof p); len = 0; } void print() { printf("%d", p[len]); for (int i = len - 1; i > 0; i--) { if (p[i] == 0) { printf("0000"); continue; } for (int k = 10; k * p[i] < mod; k *= 10) printf("0"); printf("%d", p[i]); } } }f[N][N],base[N],ans; node operator + (const node &a,const node &b){ node c; c.len = max(a.len, b.len); int x = 0; for (int i = 1; i <= c.len; i++) { c.p[i] = a.p[i] + b.p[i] + x; x = c.p[i] / mod; c.p[i] %= mod; } if (x > 0) c.p[++c.len] = x; return c; } node operator * (const node &a,const int &b){ node c; c.len = a.len; int x = 0; for (int i = 1; i <= c.len; i++) { c.p[i] = a.p[i] * b + x; x = c.p[i] / mod; c.p[i] %= mod; } while (x > 0) c.p[++c.len] = x % mod, x /= mod; return c; } node max(const node &a,const node &b){ if (a.len > b.len) return a; else if (a.len < b.len) return b; for (int i = a.len; i > 0; i--) if (a.p[i] > b.p[i]) return a; else if (a.p[i] < b.p[i]) return b; return a; } inline void basetwo(){ base[0].p[1]=1,base[0].len=1; for(int i=1;i<=m+2;i++){ base[i]=base[i-1]*2; } } inline int read(){ int p=0,f=1; char ch=getchar(); while(!isdigit(ch)) f*=(ch=='-')? -1:1,ch=getchar(); do p=(p<<1)+(p<<3)+(ch^48),ch=getchar(); while(isdigit(ch)); return p*f; } int main(){ n=read(),m=read(); basetwo(); while(n--){ memset(f,0,sizeof f); for(int i=1;i<=m;i++) ar[i]=read(); for(int i=1;i<=m;i++){ for(int j=m;j>=i;j--){ f[i][j] = max(f[i][j], f[i - 1][j] + base[m - j + i - 1] * ar[i - 1]); f[i][j] = max(f[i][j], f[i][j + 1] + base[m - j + i - 1] * ar[j + 1]); } } node Max; for (int i = 1; i <= m; i++) Max = max(Max, f[i][i] + base[m] * ar[i]); ans = ans + Max; } ans.print(); return 0; }
第四题 树网的核
题目描述
设T=(V,E,W)T=(V,E,W)T=(V,E,W)是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称TTT为树网(treebetwork
),其中VVV,EEE分别表示结点与边的集合,WWW表示各边长度的集合,并设TTT有nnn个结点。
路径:树网中任何两结点aaa,bbb都存在唯一的一条简单路径,用d(a,b)d(a, b)d(a,b)表示以a,ba, ba,b为端点的路径的长度,它是该路径上各边长度之和。我们称d(a,b)d(a, b)d(a,b)为a,ba, ba,b两结点间的距离。
D(v,P)=min{d(v,u)}D(v, P)=min{d(v, u)}D(v,P)=min{d(v,u)}, uuu为路径PPP上的结点。
树网的直径:树网中最长的路径成为树网的直径。对于给定的树网TTT,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距ECC(F)mathrm{ECC}(F)ECC(F):树网T中距路径F最远的结点到路径FFF的距离,即
ECC(F)=max{d(v,F),v∈V}mathrm{ECC}(F)=max{d(v, F),v in V}ECC(F)=max{d(v,F),v∈V}
任务:对于给定的树网T=(V,E,W)T=(V, E, W)T=(V,E,W)和非负整数sss,求一个路径FFF,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过sss(可以等于s),使偏心距ECC(F)ECC(F)ECC(F)最小。我们称这个路径为树网T=(V,E,W)T=(V, E, W)T=(V,E,W)的核(Core
)。必要时,FFF可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
下面的图给出了树网的一个实例。图中,A−BA-BA−B与A−CA-CA−C是两条直径,长度均为202020。点WWW是树网的中心,EFEFEF边的长度为555。如果指定s=11s=11s=11,则树网的核为路径DEFG
(也可以取为路径DEF
),偏心距为888。如果指定s=0s=0s=0(或s=1s=1s=1、s=2s=2s=2),则树网的核为结点FFF,偏心距为121212。
图片来自洛谷)
输入格式
共nnn行。
第111行,两个正整数nnn和sss,中间用一个空格隔开。其中nnn为树网结点的个数,sss为树网的核的长度的上界。设结点编号以此为1,2,…,n1,2,…,n1,2,…,n。
从第222行到第nnn行,每行给出333个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2472 4 7247”表示连接结点222与444的边的长度为777。
输出格式
一个非负整数,为指定意义下的最小偏心距。
输入输出样例
5 2 1 2 5 2 3 2 2 4 4 2 5 3
5
8 6 1 3 2 2 3 2 3 4 6 4 5 3 4 6 4 4 7 2 7 8 3
5
说明/提示
40%40\%40%的数据满足:5≤n≤155 le n le 155≤n≤15
70%70\%70%的数据满足:5≤n≤805 le n le 805≤n≤80
100%100\%100%的数据满足:5≤n≤300,0≤s≤10005 le n le 300,0 le s le 10005≤n≤300,0≤s≤1000。边长度为不超过100010001000的正整数
NOIP 2007 提高第四题
小博文我对不起你啊 明明你都给我讲过我还是不会做啊(懒得写)
考场搞了一手第三题后没时间把板子打出来了...
思路:
总之,要找核,先是要找树的直径(最长链),找树的直径只需要先从一个点(随便是什么)遍历,找到距离它最远的一个点,那么这个点肯定就是树的直径的一端了,从找到的这一端再遍历一次,找到距离这个端点最远的点,这个点就是另外一端了,这个应该都会搞
在遍历第二次的时候就将树改成了以直径一个端点为根的有根树,这样直径上每个点就可以通过父亲节点的记录来枚举路径
枚举过程中求出最小偏心距
总结:这个难度其实放在压轴题还算好...
#include<cstdio> #include<algorithm> using namespace std; const int N=3012; int edge,n,s,head[N],vet[N*2],val[N*2],nex[N*2],a[N],fa[N],dis[N]; bool f[N]; inline void addedge(int u,int v,int c) { nex[++edge]=head[u]; head[u]=edge; vet[edge]=v; val[edge]=c; } inline void dfs(int u,int father) { int e,v; for (e=head[u];v=vet[e],e;e=nex[e]) if (v!=father) { fa[v]=u; dis[v]=dis[u]+val[e]; dfs(v,u); } } inline void dfs1(int u,int father,int id) { int e,v; for (e=head[u];v=vet[e],e;e=nex[e]) if (v!=father&&!f[v]) { a[id]=max(a[id],dis[v]-dis[u]); dfs1(v,u,id); } } int main() { int x,y,z; scanf("%d%d",&n,&s); for (int i=1; i<n; i++) { scanf("%d%d%d",&x,&y,&z); addedge(x,y,z); addedge(y,x,z); } dfs(1,0); int l=0,r=0; for (int i=1; i<=n; i++) if(dis[i]>dis[l])l=i; for (int i=1; i<=n ;i++)dis[i]=0; dfs(l,0); for (int i=1; i<=n; i++) if (dis[i]>dis[r]) r=i;//两次dfs求直径 fa[l]=0;//注意一定要把根的父亲重置为0,因为在第一次dfs中它是一个结点 for (int i=r; i; i=fa[i]) f[i]=1;//把直径上的点标记 for (int i=r; i; i=fa[i]) dfs1(i,0,i);//求a[] int ans=0,mn=100000012; for (int i=r; i; i=fa[i]) for (int j=i; j; j=fa[j]) { if (dis[i]-dis[j]>s) break;//优化 ans=max(dis[j],dis[r]-dis[i]);//特殊点 for (int k=i; k!=fa[j]; k=fa[k]) ans=max(ans,a[k]); mn=min(ans,mn); } printf("%d ",mn); return 0; }
最后 谢谢我前面的两位美女催促我写博客 不然我是不会写的哦
超谢谢你们的(╬ ̄皿 ̄)