您的位置:首页 > 其它

NOIP2017 模拟考试 day2 2017.10.07

2017-10-08 12:14 429 查看
day2的题就有一点点有趣了。

T1dp/贪心,T2压维+单调队列,T3dijkstra/floyd打表。

怎么说呢。。。T1是我自己不谨慎的问题,仔细想想就可以发现贪心是错的,最后还好只卡了2个点。T2的话想了另一个方法但是怕TL就没有写,优化还写错了,后来证明我之前想的那个办法是不会TL的。。。还是对原来的东西总结不够。T3乱搞了100分出来但是实际上好好分析起来会发现很多有意思的东西(请不要在意题目描述)。

————————————————————————————————————————————————————————

T1(分组 team):

【问题描述】

  Mr_ he打算把自己棋下的 n 个选手分成若干组。每个选手都提出自己的要求,那就是第 i 个选手要求自己所属的组的人数大等于a[i]人。在满足所有选手的要求的前提下,Mr_he打算最大化组的总数,请你来帮助他!。注意,分组时每个选手属于且仅属于一组。

【输入格式】

  第一行一个整数 n, 表示人数。以下 n 行, 每行一个整数表示 a[i]。

【输出格式】

  输出队伍总数的最大值。 数据保证有解。

【输入样例】

5

2

1

2

2

3

【输出样例】

2

【数据范围】

对于 20%的数据, n <= 10

对于 40%的数据, n <= 1000

对于 60%的数据, n <= 10000

对于 100%的数据, 1 <= n <= 10^6

————————————————————————————————————————————————————————

可能是出于某种无脑的本能,一开始就想到要贪心去了,实际上最粗暴的贪心很好想就是把需求从大到小排序来直接选人。但是可以被卡掉,比如这组数据:1 1 1 3 4 4 4 4,发现一贪就是3组不巧可以分成4组。

有一种贪心是从小到大排序,然后一直选直到当前满足这组里面所有人的要求为止。出现最后有人分不完的时候就把上一次分组的人合并到这一组里面来,直到分组人数符合要求位为止。

后来还是写的dp,从小到大排序,设f(i)表示以i为结尾的元素中最大分组数量,g(i)表示前i个元素中最大的f值,f(i)=g(i-A[i])+1 ,g(i)=max(g(i-1),f(i))。这样就处理了最开始贪心的不足,防止前面几个人的要求少导致可以分出更多组来的情况出现。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=1000005;

int N,A[maxn];
int f[maxn],g[maxn];

void _scanf(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
bool cmp(int x,int y) { return x<y; }
int main()
{
freopen("team.in","r",stdin);
freopen("team.out","w",stdout);
_scanf(N);
for(int i=1;i<=N;i++) _scanf(A[i]);
sort(A+1,A+N+1,cmp);
for(int i=1;i<=N;i++)
{
int j=max(0,i-A[i]);
f[i]=g[j]+1;
g[i]=max(f[i],g[i-1]);
}
printf("%d\n",f
);
return 0;
}


——————————————————————————————————————————————————————

T2(权值矩阵 matrix):

【问题描述】

  Mr_he有一个 n* m 的矩阵,并且把1~n* m 这n*m填写在这个矩阵中(注意,每个整数在矩阵出现一次,且仅出现一次)。

  Mr_he同时约定一个矩阵权值等于这个子矩阵中的所有数的最小值。

  现在Mr_he想知道,在给出的矩阵中,权值为i的子矩阵可能有多少种?

【输入格式】

  第一行, 两个整数 N, M。接下来的 N 行, 每行 M 个整数, 表示矩阵中的元素。

【输出格式】

  N×M 行, 每行一个整数, 其中第 i 行的整数表示如果小 A 选择的子矩阵权值为 i, 他选择的子矩阵的种类数。

【输入样例】

2 3

2 5 1

6 3 4

【输出样例】

6

4

5

1

1

1

【样例解释】

  balabalabala(好吧只是我不想粘图而已AuA)

  其他情况不再赘述!

【数据范围】

对于 30%的数据, 1<=N, M<=50;

对于全部的数据, 1<=N, M<=300。

———————————————————————————————————————————————————————

唉想着就气,这个题的分数梯度给的一点用都没有,想到了30分还想不到100吗真的是。。。。(你们懂的)

枚举矩阵的上下行(简单来说就是两根线),对于两根线之间形成的矩阵我们发现实际上只有每一列上最小的元素可能是以这两根线为上下界的矩阵中的最小值,所以压缩一下就成了一个序列了。

思考序列上怎么办(好吧这个应该是先想的),可以发现对于一个位置i的元素以其为最小值的线段的端点只可能在L[i]+1和R[i]-1,L[i]表示i左边第一个小于它的元素的下标,R[i]类似。然后以这个点i为最小元素的线段数量就应该是(i-L[i])*(R[i]-i)。这个直接用单调队列就好了。

也就是说对于每一种上下界枚举的情况我们都可以算出这个上下界对某一个元素最终答案的贡献,最后枚举完之后答案自然就出来了。

我不知道我当时怎么想的,这个题本来真的是。。。特别。。。简单。。。我想复杂了QAQ

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 1e9+5
using namespace std;
const int maxn=305;

int N,M,A[maxn][maxn];
int ans[maxn*maxn],minv[maxn];
struct data{ int pos,v; }q[maxn];
int front,rear,L[maxn],R[maxn];

void _scanf(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void data_in()
{
_scanf(N);_scanf(M);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++) _scanf(A[i][j]);
}
void calc()
{
front=rear=0;
for(int j=1;j<=M;j++)
{
while(front!=rear&&q[rear-1].v>minv[j]) rear--;
L[j]= front!=rear ? q[rear-1].pos : 0<
136c0
/span>;
q[rear++]=(data){j,minv[j]};
}
front=rear=0;
for(int j=M;j>=1;j--)
{
while(front!=rear&&q[rear-1].v>minv[j]) rear--;
R[j]= front!=rear ? q[rear-1].pos : M+1;
q[rear++]=(data){j,minv[j]};
}
}
void work()
{
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++) minv[j]=inf;
for(int ii=i;ii<=N;ii++)
{
for(int j=1;j<=M;j++) minv[j]=min(minv[j],A[ii][j]);
calc();
for(int j=1;j<=M;j++)
ans[minv[j]]+=(j-L[j])*(R[j]-j);
}
}
for(int i=1;i<=N*M;i++)
printf("%d\n",ans[i]);
}
int main()
{
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
data_in();
work();
return 0;
}


————————————————————————————————————————————————————————

T3(过路费 roadtoll):

【问题描述】

  为了发财,Mr_he在他的农场设置了一系列的规章制度,使得任何一只奶牛在农场中的道路行走,都要向他交过路费。

  农场中有 n 片草地(编号为1~n),并且有 m 条双向道路把这些草地连接起来。Mr_he已经在每条双向道路上设置一个过路费wi。可能有多条道路连接相同的两片草地,但是不存在一条道路连接一片草地和这片草地本身。最值得庆幸的是,奶牛从任意一片草地出发,经过一系列的路径,总是可以抵达其他任意一片草地。

  为了最大化自己的收入。Mr_he在每片草地上也设置了一个过路费用ci。并规定,奶牛从一片草地到另一片草地需要交纳的费用,是经过的所有道路的费用之和,加上经过的所有草地(包括起点和终点)的过路费的最大值。

  任劳任怨的牛们希望去调查一下,它们每一次的出行应该选择那条路径才能最节俭。她们要你写一个程序,接受 k 个问题并且输出每个询问对应的最小花费。第 i 个问题包含两个数字 s,t,表示起点和终点草地。

【输入格式】

  第 1 行:三个用空格隔开的整数:n,m,k。

  第 2 到第 n+1 行:第 i+1 行包含一个单独的整数 c(1<=c<=100000),表示第 i 片草地的费用。

  第 n+2 行到第 n+m+1 行:第 j+n+1 行包含 3 个用空格隔开的整数:a,b,w(1<=a,b<=n,1<=w<=100000),表示一条道路(a,b)的费用为 w 。

  第 n+m+2 到第 n+m+k+1 行:第 i+n+m+1 行表示第 i 个问题,包含两个用空格隔开的整数 s 和 t(1<=s,t<=n 且 s!=t),表示一条询问的起点和终点。

【输出格式】

  第 1 到第 k 行:第 i 行包含一个整数,表示从 s 到 t 的最小花费。

【输入样例】

5 7 3

2

5

3

3

4

1 2 3

1 3 2

2 5 3

5 3 1

5 4 1

2 4 3

3 4 4

1 3

1 4

2 3

【输出样例】

5

8

9

【样例解释】

。。不给!!!!(滑稽)

————————————————————————————————————————————————————

我当时一看就是floyd的感觉就没有想太多就写了,写出来之后感觉不是很靠谱就打了个SPFA对了一下拍,发现floyd跑出来要大一点。没有检查出来,就再floyd了一遍。诶好像出错率减少了,再来一遍。。。。

就没有错了。。。。

上面都是乱搞,下面好好分析一下。

根据题目交代的路径费用的定义,可以想到枚举每一个点作为权值最大点的情况。这样就可以保证算出来的结果是正确的了(移动到这个最大点肯定是要用最短路啦)。但是显然每一组询问都去算的话是会超时的。面对10000组询问,可以想到打表。那么在知道这个点是最大值路径上的最大值并求出单源最短路之后怎么办呢?显然对于任意一条以点k为最大点值的点的路径i->j,可以拆分成i->k和k->j,那么就是说在这种情况下我们可以更新i->j的最小费用路径了。时间复杂度O(N*MlogN+N^3) ,已经可以AC,而且由于有剪枝条件的存在一般情况下达不到这个时间。

下面是dijkstra的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 1e8+5
using namespace std;
const int maxn=255;
const int maxm=10005;

int N,M,K,C[maxn];
int dist[maxn][maxn];
struct edge{ int to,next,w; }E[maxm<<1];
int first[maxn],np;
int f[maxn],_f[maxn];
struct state{ int id,v; };
struct cmp{
bool operator () (state a,state b)
{
return a.v>b.v;
}
};

void _scanf(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void add_edge(int u,int v,int w)
{
E[++np]=(edge){v,first[u],w};
first[u]=np;
}
void data_in()
{
_scanf(N);_scanf(M);_scanf(K);
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
dist[i][j]=inf;
for(int i=1;i<=N;i++) _scanf(C[i]);
int x,y,z;
for(int i=1;i<=M;i++)
{
_scanf(x);_scanf(y);_scanf(z);
add_edge(x,y,z);
add_edge(y,x,z);
}
}
void dijkstra(int s)
{
for(int i=1;i<=N;i++) f[i]=inf;
priority_queue<state,vector<state>,cmp>pq;
pq.push((state){s,0});
f[s]=0;
state tmp;
int i,j;
while(!pq.empty())
{
tmp=pq.top();pq.pop();
i=tmp.id;
if(f[i]<tmp.v) continue;
for(int p=first[i];p;p=E[p].next)
{
j=E[p].to;
if(C[j]>C[s]) continue;
if(f[i]+E[p].w<f[j])
{
f[j]=f[i]+E[p].w;
pq.push((state){j,f[j]});
}
}
}
}
void work()
{
for(int i=1;i<=N;i++)
{
dijkstra(i);
for(int j=1;j<=N;j++)
for(int k=1;k<=N;k++)
dist[j][k]=min(dist[j][k],f[j]+f[k]+C[i]);
}
int x,y;
for(int i=1;i<=K;i++)
{
_scanf(x);_scanf(y);
printf("%d\n",dist[x][y]);
}
}
int main()
{
freopen("roadtoll.in","r",stdin);
freopen("roadtoll.out","w",stdout);
data_in();
work();
return 0;
}


再分析一下,发现上面这个东西。。。。和floyd好像有没有?!允许某个点作为中间点然后balabalabala……

所以把所有的点按照权值由小到大排序之后就得到了floyd的算法了。注意到算最大权值的时候floyd里面要用到两个端点的权值来决策。

下面是floyd的代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 1e8+5
using namespace std;
const int maxn=255;

int N,M,K,C[maxn];
int g[maxn][maxn],dist[maxn][maxn];
struct data{ int id,v; }p[maxn];

void _scanf(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void data_in()
{
_scanf(N);_scanf(M);_scanf(K);
for(int i=1;i<=N;i++) _scanf(C[i]);
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
if(i!=j) g[i][j]=inf;
int x,y,z,t;
for(int i=1;i<=M;i++)
{
_scanf(x);_scanf(y);_scanf(z);
t=min(g[x][y],z);
g[x][y]=g[y][x]=t;
}
}
bool cmp(data x,data y) { return x.v<y.v; }
void floyd()
{
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
if(i!=j) dist[i][j]=inf;
for(int k=1;k<=N;k++)
{
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
if(g[i][p[k].id]+g[p[k].id][j]<g[i][j])
g[i][j]=g[i][p[k].id]+g[p[k].id][j];
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
{
int maxc=max(p[k].v,max(C[i],C[j]));
if(g[i][p[k].id]+g[p[k].id][j]+maxc<dist[i][j])
dist[i][j]=g[i][p[k].id]+g[p[k].id][j]+maxc;
}
}
}
void work()
{
for(int i=1;i<=N;i++)
p[i]=(data){i,C[i]};
sort(p+1,p+N+1,cmp);
floyd();
int x,y;
for(int i=1;i<=K;i++)
{
_scanf(x);_scanf(y);
printf("%d\n",dist[x][y]);
}
}
int main()
{
freopen("roadtoll.in","r",stdin);
freopen("roadtoll.out","w",stdout);
data_in();
work();
return 0;
}


然后还有一个变化的版本,把上面的最大权值改成次大,怎么办呢?

floyd就失效了ORZ(反正我是没有写出来的),只能dijkstra打表。第一步还是算单源情况下某个点为大点的最短路,过程中把C[j]>=C[s]的点(s是起点)放进一个队列,然后用这些元素作为起始值来求最短路(不想写在一起)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
#define inf 1e8+5
using namespace std;
const int maxn=255;
const int maxm=10005;

int N,M,K,C[maxn];
int dist[maxn][maxn];
struct edge{ int to,next,w; }E[maxm<<1];
int first[maxn],np;
int f[maxn],_f[maxn];
struct state{ int id,v; };
struct cmp{
bool operator () (state a,state b)
{
return a.v>b.v;
}
};
queue<state>q;

void _scanf(int &x)
{
x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void add_edge(int u,int v,int w)
{
E[++np]=(edge){v,first[u],w};
first[u]=np;
}
void data_in()
{
_scanf(N);_scanf(M);_scanf(K);
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
dist[i][j]=inf;
for(int i=1;i<=N;i++) _scanf(C[i]);
int x,y,z;
for(int i=1;i<=M;i++)
{
_scanf(x);_scanf(y);_scanf(z);
add_edge(x,y,z);
add_edge(y,x,z);
}
}
void dijkstra(int s)
{
for(int i=1;i<=N;i++) f[i]=inf;
priority_queue<state,vector<state>,cmp>pq;
pq.push((state){s,0});
f[s]=0;
state tmp;
int i,j;
while(!pq.empty())
{
tmp=pq.top();pq.pop();
i=tmp.id;
if(f[i]<tmp.v) continue;
for(int p=first[i];p;p=E[p].next)
{
j=E[p].to;
if(C[j]>=C[s]) q.push((state){j,f[i]+E[p].w});
if(C[j]>C[s]) continue;
if(f[i]+E[p].w<f[j])
{
f[j]=f[i]+E[p].w;
pq.push((state){j,f[j]});
}
}
}
}
void _dijkstra(int s)
{
for(int i=1;i<=N;i++) _f[i]=inf;
priority_queue<state,vector<state>,cmp>pq;
state tmp;
int i,j;
while(!q.empty())
{
tmp=q.front();q.pop();
i=tmp.id;
if(_f[i]<tmp.v) continue;
_f[i]=tmp.v;
pq.push((state){i,_f[i]});
}
while(!pq.empty())
{
tmp=pq.top();pq.pop();
i=tmp.id;
if(_f[i]<tmp.v) continue;
for(int p=first[i];p;p=E[p].next)
{
j=E[p].to;
if(C[j]>C[s]) continue;
if(_f[i]+E[p].w<_f[j])
{
_f[j]=_f[i]+E[p].w;
pq.push((state){j,_f[j]});
}
}
}
}
void work()
{
for(int i=1;i<=N;i++)
{
dijkstra(i);
_dijkstra(i);
for(int j=1;j<=N;j++)
for(int k=1;k<=N;k++)
dist[j][k]=min(dist[j][k],min(f[j]+_f[k],_f[j]+f[k])+C[i]);
}
int x,y;
for(int i=1;i<=K;i++)
{
_scanf(x);_scanf(y);
printf("%d\n",dist[x][y]);
}
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
data_in();
work();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: