2016.9.3 好好学习天天快乐解题报告
都是09年NOIP的题
考试分析:
1. 第一题真的不要想得太复杂了,多写几组数据,多观察数据范围,先排序看看是个很不错的方法;
2. 高精度一定要熟练,程序实现能力还要加强,longlong陷阱要尽力避免;
3. 考虑全面,多想想会不会有其他情况多画图,不好实现的要看看能不能剪枝;
4. 代码实现能力,那几个难的地方都要多练,把握好时间就不会有什么大问题,longlong陷阱一定要小心,即使想出来解法了也要多画图自己多跟着走想想程序能不能解决这种情况(不管是范围还是算法)
解题报告:
一.统计数字:
题意:给定数字,按数字大小顺序输出某个数字出现次数;每个数均不超过1500000000(1.5*109)。已知不相同的数不超过10000 个;
分析:常见的快排使有序+扫描,先快排使相近的数在一起(这个思维很重要),再线性扫描就行了;
程序:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,flg1,flg2,a[200005];
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+n+1);
flg1=1;flg2=a[1];
for(int i=2;i<=n;i++)if(a[i]==flg2)flg1++;
else{
printf("%d %d\n",flg2,flg1);flg2=a[i];flg1=1;
}
printf("%d %d",flg2,flg1);
return 0;
}
二.矩阵取数游戏:
题意:n*m的矩阵,每次取两边尽头的数,乘以一个单调递增的权值,使得权值最大;60%的数据满足:1<=n, m<=30,答案不超过1016
100%的数据满足:1<=n, m<=80,0<=aij<=1000
分析:刚开始容易想贪心,但是既不符合单调又不符合完全无后效,所以用贪心的兄弟DP,数据范围很明显看得出要用高精度;DP用类似状压的方法,分别表示前,后取了的数,然后可选择的状态就是前面-1或者后面-1;10进制高精过不了,要万进;
程序:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[85][35],ovo[85][85],n,m;//数据范围;
int f[85][85][35],tot[35],tmp[35],ans[35];
void chengfa1(int u)
{
for(int i=1;i<=30;i++){
a[u][i]+=a[u-1][i]*2;a[u][i+1]+=a[u][i]/1000;a[u][i]%=1000;
}
}
void chengfa2(int j,int k,int u)
{
int tired=j+k;
for(int i=1;i<=30;i++){
f[j][k][i]+=a[tired][i]*ovo[u][j];
f[j][k][i]+=f[j-1][k][i];
f[j][k][i+1]+=f[j][k][i]/1000;
f[j][k][i]%=1000;
}
}
int cmp(int j,int k,int u)
{
memset(tmp,0,sizeof(tmp));
int tired=j+k;
for(int i=1;i<=30;i++){
tmp[i]+=a[tired][i]*ovo[u][m-k+1];
tmp[i]+=f[j][k-1][i];
tmp[i+1]+=tmp[i]/1000;
tmp[i]%=1000;
}
for(int i=30;i>=1;i--)if(tmp[i]>f[j][k][i])return 1;
else if(tmp[i]<f[j][k][i])return 0;//一定要在第一个不等的地方返回;
return 0;
}
void exchange(int j,int k)
{
for(int i=1;i<=30;i++)f[j][k][i]=tmp[i];
}
void chengfa3(int j,int k,int u)
{
int tired=j+k;
for(int i=1;i<=30;i++){
f[j][k][i]+=a[tired][i]*ovo[u][m-k+1];
f[j][k][i]+=f[j][k-1][i];
f[j][k][i+1]+=f[j][k][i]/1000;
f[j][k][i]%=1000;
}
}
int cmp2(int j)
{
for(int i=30;i>=1;i--)if(f[j][m-j][i]>ans[i])return 1;
else if(ans[i]>f[j][m-j][i])return 0;
return 0;
}
void exchange2(int j)
{
for(int i=30;i>=1;i--)ans[i]=f[j][m-j][i];
}
void jiafa()
{
for(int i=1;i<=30;i++){
tot[i]+=ans[i];
tot[i+1]+=tot[i]/1000;//+=
tot[i]%=1000;
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d %d",&n,&m);a[1][1]=2;
for(int i=2;i<=80;i++)chengfa1(i);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&ovo[i][j]);
for(int i=1;i<=n;i++){memset(f,0,sizeof(f));//初始化;
for(int j=0;j<=m;j++){
int lim=m-j;
for(int k=0;k<=lim;k++)if(j&&k){
chengfa2(j,k,i);if(cmp(j,k,i))exchange(j,k);
}
else if(j)chengfa2(j,k,i);
else if(k)chengfa3(j,k,i);
}
memset(ans,0,sizeof(ans));
for(int j=0;j<=m;j++)
if(cmp2(j))exchange2(j);
jiafa();
}
int uu=30;
while(!tot[uu]&&uu>=1)uu--;
if(uu){
printf("%d",tot[uu]);
for(uu-=1;uu>=1;uu--)if(tot[uu]/100)printf("%d",tot[uu]);
else if(tot[uu]/10)printf("0%d",tot[uu]);
else printf("00%d",tot[uu]);}
else printf("0");
return 0;
60分的程序:
#include<iostream>
#include<cstdio>
using namespace std;
int a[35],ovo[35][35],n,m;
long long f[35][35],tot;
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d %d",&n,&m);a[1]=2;
for(int i=2;i<=30;i++)a[i]=2*a[i-1];
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&ovo[i][j]);
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
int lim=m-j;
for(int k=0;k<=lim;k++)if(j&&k)
f[j][k]=max((long long)f[j-1][k]+(long long)a[j+k]*ovo[i][j],(long long)f[j][k-1]+(long long)a[j+k]*ovo[i][m-k+1]);
else if(j)f[j][k]=(long long)f[j-1][k]+(long long)a[j+k]*ovo[i][j];
else if(k)f[j][k]=(long long)f[j][k-1]+(long long)a[j+k]*ovo[i][m-k+1];
}//根据运算顺序来加(longlong),最好实验一下或者直接开longlong
long long ans=0;
for(int j=0;j<=m;j++)ans=max(ans,f[j][m-j]);
tot+=ans;
}
printf("%I64d",tot);
return 0;
}
三.树网的核:
题意:在直径上找长度满足要求的链使得其余点到该链任意点的最小的最大值最小;
分析:图会有多个直径,但这道题的关键是,直径相交的点或线段的端点到其他点的最长一定是相等的,不然对于这两个端点取最长可以得到一条更长的链;所以只用扫一条直径即可,这里是贪心的思想,要多画图;然后对于某固定的端点,另一端点肯定越远,至少最长不会变长,因为多了可用来使距离变小的点,这样就不用枚举所有线段了,这是标程的做法,我因为题意理解错了用来排除的只有端点,反而必须所有边都枚举,可以证明这样得到的结果也是正确的;
程序:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int mini=2e9,ovo[305][305],ovo2[305][305],len[305],n,s,maxn,sta,fin,w,roa[305],cnt,ovo3[305][305];
int ovo4[305][305],ans=2e9,roa1,maxn1,maxn2;
void dfs(int u,int fr,int dep)
{
for(int i=1;i<=len[u];i++)if(ovo[u][i]!=fr)
dfs(ovo[u][i],u,dep+ovo3[u][ovo[u][i]]);
if(dep>maxn)roa1=u;maxn=max(dep,maxn);
}
void dfs1(int u,int fr,int dep)
{
for(int i=1;i<=len[u];i++)if(ovo[u][i]!=fr)
dfs1(ovo[u][i],u,dep+ovo3[u][ovo[u][i]]);
maxn=max(maxn,dep);
}
int dfs2(int u,int fr,int dep,int k)
{
for(int i=1;i<=len[u];i++)if(ovo[u][i]!=fr)
if(dfs2(ovo[u][i],u,dep+ovo3[u][ovo[u][i]],k+1)){
roa[k]=u;return 1;}
if(dep==maxn){
roa[k]=u;maxn2=k;return 1;}
return 0;
}
int main()
{
freopen("core.in","r",stdin);
freopen("core.out","w",stdout);
scanf("%d %d",&n,&s);memset(ovo3,127,sizeof(ovo3));
for(int i=1;i<n;i++){
scanf("%d %d %d",&sta,&fin,&w);
ovo[sta][++len[sta]]=fin;
ovo[fin][++len[fin]]=sta;
ovo3[fin][sta]=ovo3[sta][fin]=w;ovo3[i][i]=0;
}ovo3[n][n]=0;
for(int j=1;j<=n;j++)for(int i=1;i<=n;i++)for(int k=1;k<=n;k++)
ovo3[i][k]=min((long long)ovo3[i][k],(long long)ovo3[i][j]+ovo3[j][k]);
dfs(1,0,0);
dfs1(roa1,0,0);
dfs2(roa1,0,0,1); maxn1=maxn;
for(int i=1;i<=maxn2;i++)for(int j=i;j<=maxn2;j++)if(ovo3[roa[i]][roa[j]]<=s){
int jl;maxn=0;dfs1(roa[i],roa[i+1],0);jl=maxn;maxn=0;dfs1(roa[j],roa[j-1],0);
maxn=max(maxn,jl);ans=min(maxn,ans);
}
printf("%d",ans);
return 0;
}
标程:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
const int MAXN = 301;
const int INF = 100000000;
int data[MAXN][MAXN], dis[MAXN][MAXN], path[MAXN], tpath[MAXN];
int maxdis = 0, id, n, num;
bool used[MAXN];
void floydWarshall(int n, const int mat[][MAXN], int min[][MAXN]) //长宽;
{ int i, j, k;
for (i = 0; i<n; i++) {
for (j = 0; j<n; j++) {
if (mat[i][j] != 0 || i == j) min[i][j] = mat[i][j]; else min[i][j] = INF;
}
}
for (k = 0; k < n; k++) {
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
if (min[i][k] + min[k][j] < min[i][j]) {
min[i][j] = min[i][k] + min[k][j];
}
}
}
}
}
void DFS(int x, int dis, int m){
if (dis > maxdis){
maxdis = dis; id = x;
memcpy(path, tpath, (m + 1) * sizeof(int));
num = m;
}
used[x] = true;
for (int i = 0; i != n; ++i){
if (data[x][i] && !used[i]){
tpath[m] = x;
DFS(i, dis + data[x][i], m + 1);
tpath[m] = 0;
}
}
}
int calc(int x, int y){
int r = 0;
for (int i = 0; i < n; ++i){
int ans = 99999999;
for (int j = x; j <= y; ++j) if (dis[i][path[j]] < ans) ans = dis[i][path[j]];
if (ans > r) r =ans;
}
return r;
}
int main(){
freopen("core.in", "r", stdin);
freopen("core.out", "w", stdout);
int s;
cin >> n >> s;
for (int i = 1; i != n; ++i){
int x, y, c;
cin >> x >> y >> c;
x--; y--;
data[x][y] = data[y][x] = c;
}
memset(used, 0, sizeof(used)); DFS(0, 0, 0); maxdis = 0;
memset(used, 0, sizeof(used)); DFS(id, 0, 0);
floydWarshall(n, data, dis);
int open = 0, closed = 0, sum = 0, ans = 99999999;
while (closed < num){
while (sum + data[path[closed]][path[closed + 1]] <= s && closed < num){
sum += data[path[closed]][path[closed + 1]]; ++closed;
}
int temp = calc(open, closed);
if (temp < ans) ans = temp;
sum -= data[path[open]][path[open + 1]]; ++open;
}
cout << ans << endl;
}