解题报告 2017.11.5,补习题二上
开始集训,有点懵逼,开始出了三道题,做了至少一天半,最后还没200分,好像第一题50,第二题30,第三题30。
附上原题:(后有截图)
P104
zhx
: 竞赛时间:???? 年?? 月?? 日??:??-??:??
题目名称 遭遇 都市 街灯
名称 meet city light
输入 meet.in city.in light.in
输出 meet.out city.out light.out
每个测试点时限 1s 1s 1.5s
内存限制 256MB 256MB 256MB
测试点数目 10 10 10
每个测试点分值 10 10 10
是否有部分分 无 无 无
题目类型 传统 传统 传统
注意 事项) (请务必仔细阅读) :
P104 zhx 遭遇
第 2 页 共 6 页
遭遇
【问题描述】
你是能看到第一题的 friends 呢。
——hja
𝑂座楼房,立于城中。
第𝑖座楼,高度ℎ 𝑖 。
你需要一开始选择一座楼,开始跳楼。在第𝑖座楼准备跳楼需要𝑐 𝑖 的花费。
每次可以跳到任何一个还没有跳过的楼上去。但跳楼是有代价的,每次跳到另
外一座楼的代价是两座楼高度的差的绝对值,最后一次从楼上跳到地面上不需
要代价(只能跳到地上一次)。为在代价不超过𝑇的情况下,最多跳几次楼。
(一座楼只能跳一次,且每次跳楼都要计算准备的花费)
【输入格式】
第一行一个整数𝑂,楼的数量。
𝑐 𝑖 。
接下来一行𝑂个整数代表ℎ 𝑖 。
最后一行一个整数𝑇。
【输出格式】
一行一个整数代表答案。
【样例输入】
4
3 5 4 11
2 1 3 1
17
【样例输出】
3
【样例解释】
从1号楼跳到2号楼再跳到3号楼是一种可行的方案。
【数据范围与规定】
30%的数据,1 ≤ 𝑂 ≤ 5。
所有ℎ 𝑖 相同。
对于另外20%的数据,𝑐 𝑖 = 0。
P104 zhx 遭遇
第 3 页 共 6 页
对于100%的数据,1 ≤ 𝑂 ≤ 50,1 ≤ 𝑐 𝑖 ,ℎ 𝑖 ≤ 10 6 ,1 ≤ 𝑇 ≤ 10 7 。
其实我第一次看这个题,心里很蒙,这和题目有啥关系?但后来看两个题我就释然了。
话不多说,附上代码:
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
using namespace std;
struct AT{
int h[52];
int c[52];
bool b[52];
}A;
int N,T,cishu=0,w[52][52],total,ma=0;
priority_queue<int>q;
int abs(int xx,int yy){
if(xx>=yy)
return xx-yy;
return yy-xx;
}
void digui(int x){
int ll=0;
if(cishu>ma) ma=cishu;
A.b[x]=1;
for(int i=1;i<=N;i++)
if(A.b[i]==0&&w[x][i]<=total&&i!=x)
{ll=1;
cishu++;
total-=w[x][i];
digui(i);
total+=w[x][i];
cishu--;
}
if(ll==0&&total>=A.c[x])
{cishu++;
if(cishu>ma) ma=cishu;
cishu--;
}
A.b[x]=0;
}
int main(){
int ci=0,hi=0,Ni=0,pb;
freopen("meet.in","r",stdin);
freopen("meet.out","w",stdout);
memset(A.b,0,sizeof(A.b));
memset(A.h,0,sizeof(A.h));
memset(A.c,0,sizeof(A.c));
memset(w,0,sizeof(w));
scanf("%d",&N);
for(int i=1;i<=N;i++)
scanf("%d",&A.c[i]);
for(int i=1;i<=N;i++)
scanf("%d",&A.h[i]);
scanf("%d",&T);//输入完成,开始判断
pb=A.h[1];
for(int i=1;i<=N;i++)
{if(A.c[i]!=0) ci=1;
if(A.h[i]!=pb) hi=1;
}
//开c=0;
if(ci==0){
int cc,guoqu;
for(int i=1;i<=N;i++)
q.push(-A.h[i]);
for(int i=1;i<=N;i++){
guoqu=-A.h[i];
int lq=0;
while(T>0&&q.empty()!=true)
{cc=q.top();q.pop();
if(cc==A.h[i]&&lq==0) {lq=1; continue ;
}
T-=guoqu-cc;
guoqu=cc;
cishu++;
if(cishu>ma) ma=cishu;
}}
if(T>=0)
printf("%d",ma+1);
else printf("%d",ma);
fclose(stdin);
fclose(stdout);
return 0;
}
if(hi==0){ int vv;
for(int i=1;i<=N;i++)
q.push(-A.c[i]);
total=T;
while(total>0&&q.empty()!=true)
{vv=q.top();q.pop();
total-=-vv;
cishu++;
}
if(total>=0)
printf("%d",cishu);
else printf("%d",cishu-1);
fclose(stdin);
fclose(stdout);
return 0;
}
total=T;
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
if(i!=j)
w[i][j]=A.c[i]+abs(A.h[i],A.h[j]);
for(int i=1;i<=N;i++)
digui(i);
printf("%d",ma);
fclose(stdin);
fclose(stdout);
return 0;
//N判定完毕,开始h和c
}
这个题由于数据范围有些特殊,所以要分成三块,分别讨论(正解好像不是),好像数据有点问题,不然应该就七十了。这个题我写了一上午,由于对临界条件(即大于等于还是大于等)不太理解,所以第一次全错。本来想最后纯暴力来,结果都超时了。优先队列如果不重载,那就默认从小到大排,如果想让其从大到小,可将它们的相反数推入,然后拿时用负数。
优先队列使用方法:
重载:
bool operator <(const AT &xi,const AT &yi) {
return xi.chu>yi.chu;
}
必须用结构体!!
命名:priority_queue<AT> dui; 其中如果队列为int型,不用重载;
推进东西:dui.push(x);
队列是否为空:dui.empty()
队列第一个:dui.top()
删除队列第一个:dui.pop();
以后如果给了某些数据范围,可分成好几块,能拿一分是一分。
标准代码:
分析:暴力挺好打的,对于前30%的数据神搜,hi相同的数据将所有的建筑按照c从小到大排序,看最多能跳多少,ci=0的数据将所有的建筑按照h从小到大排序,枚举起点和终点,看能否跳这么多,取个max就可以了.这样70分就到手了.
部分分的提示还是比较明显的,要消除一个参数的影响,那么就按照h从小到大排序,显然只有可能顺着跳过城市,不能跳过去又跳回来.那么就是一个比较简单的dp了:f[i][j]表示跳了i次,最后一次跳到j的最小花费,转移的话枚举j之前的k就能转移了,最后倒叙枚举i看哪一个f[i][j]<=T就可以了.
多个参数有影响的常见策略是消除一个参数的影响,常见的办法就是排序,如果一个点不能经过多次,那么就想一个办法让它强行不经过这个点.
正解:
复制代码
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,T;
int f[60][60];
struct node
{
int c, h;
}e[60];
bool cmp(node a, node b)
{
return a.h < b.h;
}
int main()
{
memset(f, 127 / 3, sizeof(f));
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &e[i].c);
for (int i = 1; i <= n; i++)
scanf("%d", &e[i].h);
scanf("%d", &T);
sort(e + 1, e + 1 + n, cmp);
f[0][1] = e[1].c;
for (int i = 0; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = j + 1; k <= n; k++)
f[i + 1][k] = min(f[i + 1][k], f[i][j] + e[k].h - e[j].h + e[k].c);
for (int i = n; i >= 0; i--)
for (int j = 1; j <= n; j++)
if (f[i][j] <= T)
{
printf("%d\n", i + 1);
return 0;
}
printf("0\n");
return 0;
}
P104 zhx 都市
第 4 页 共 6 页
都市
【问题描述】
你是能看到第二题的 friends 呢。
——laekov
塔立于都市,攀登上塔,能够到达更远的地方。但是上塔,需要破解谜
题。仍然有𝑂个数,但并不给你,而是给了你𝑂 ×
𝑁−1
2
个数,代表它们两两的
和。那么,这𝑂个数是多少呢?
【输入格式】
一行一个整数𝑂。
接下来一行𝑂 ×
𝑁−1
2
个数,代表两两之和。
【输出格式】
第一行一个整数𝑡代表解的个数。
接下来𝑡行,每行𝑂个数代表一组解,数从小到大排列。解的顺序按照字典序
从大到小排列。
【样例输入 1】
4
3 5 4 7 6 5
【样例输出 1】
1
1 2 3 4
【样例输入 2】
4
11 17 21 12 20 15
【样例输出 2】
2
4 7 8 13
3 8 9 12
P104 zhx 都市
第 5 页 共 6 页
【数据范围与规定】
3,𝑂个数均不超过10。
60%的数据,1 ≤ 𝑂 ≤ 50,𝑂个数均不超过100。
对于100%的数据,1 ≤ 𝑂 ≤ 300,𝑂个数均不超过10 8 。
对于这个题,I want to say nothing。八小时,三十分。
其实我只做了四个小时,但又检查了四个小时,该三十分还是三十。
这个题错的原因挺多,首先sort排序不太会,
其次平心而论,这个题解法实在太难想了,好几种解让瞎猫碰上死耗子可能性几乎为0,而暴力又害怕超时.etc.
不知到为什么,我后几个点的输出全错,并非字典序,而是在主函数就错了。
并且,我字典序又不知道怎么写,我感觉自己至少花了两个小时来写字典序,浑浑噩噩,好像还错了。
因为有时候几组解都是相同的,按字典序,就只能输出一种,并种类是一,而按我解法,会有3+种,我花了两个小时,就解决了这一个点。QAQ
好了,话不多说,附上我的程序(字典序可能错了):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct AT{
int shu;
bool p;
}zx[45002];
bool cmp(const AT &x,const AT &y)
{return x.shu<y.shu;
}
struct VV{
int ans[302];
bool wu;
}w[302];
int N,mi=10000,ban=1,ll=0,lll=0,remember,qq,kk;
int erf(int s,int e,int xu){
int m=(s+e)/2;
if(e-s<=1){
if(zx[s].shu==xu&&zx[s].p==0) {zx[s].p=1; return 0;}
if(zx[e].shu==xu&&zx[m].p==0) {zx[e].p=1; return 0;}
return -1;
}
if(zx[m].shu>xu)
return erf(s,m,xu);
if(zx[m].shu<xu)
return erf(m,e,xu);
zx[m].p=1;
return 0;
}
//zidianxuhanshukaishi
int compare(int aa,int bb){
for(int i=1;i<=N;i++)
{if(w[aa].ans[i]>w[bb].ans[i]) return aa;
if(w[aa].ans[i]<w[bb].ans[i]) return bb;
}
return 0;
}
void zdx(int x){
int lol=0,lpl,ssg;
VV wc;
for(int i=1;i<ban;i++)
{lol=0;
ssg=1;
for(int jj=1;jj<ban;jj++)
if(w[jj].wu==0){if(lol==w[jj].ans[x])
{ssg=compare(jj,lpl);
if(ssg==0) {
for(int j=jj+1;j<ban;j++)
w[j-1]=w[j];
ban--; }
else lpl=ssg;
}if(lol<w[jj].ans[x]){lol=w[jj].ans[x]; lpl=jj;}
}
if(lol!=0){
wc=w[i];
w[i]=w[lpl];
w[lpl]=wc;
}
}
printf("%d\n",ban-1);
for(int i=1;i<ban;i++)
{for(int j=1;j<=N;j++)
printf("%d ",w[i].ans[j]);
printf("\n");
}
}
//zidianxuhanshujieshu
int main(){
freopen("city.in","r",stdin);
//freopen("city.out","w",stdout);
memset(zx,0,sizeof(zx));
memset(w,0,sizeof(w));
scanf("%d",&N);
for(int i=1;i<=N*(N-1)/2;i++)
scanf("%d",&zx[i].shu);
sort(zx+1,zx+N*(N-1)/2+1,cmp);
zx[1].p=zx[2].p=1;
for(int i=3;i<=N;i++)
{for(int j=3;j<=N*(N-1)/2;j++)
zx[j].p=0;
ll=3;
qq=0;
zx[i].p=1;//先解一波方程
if((zx[2].shu-zx[i].shu+zx[1].shu)%2==0&&(zx[2].shu-zx[i].shu+zx[1].shu)/2>=0)
{
w[ban].ans[1]=(zx[2].shu-zx[i].shu+zx[1].shu)/2;
w[ban].ans[2]=zx[1].shu-w[ban].ans[1];
w[ban].ans[3]=zx[2].shu-w[ban].ans[1];//解出了前三个
//要不开波函数?
for(int vv=ll+1;vv<=N;vv++){
mi=100000;
for(int j=3;j<=N*(N-1)/2;j++)
if(mi>zx[j].shu&&zx[j].p==0) {mi=zx[j].shu; remember=j; }
ll++;
zx[remember].p=1;
w[ban].ans[ll]=mi-w[ban].ans[1];
for(int jj=2;jj<ll;jj++)
if(erf(1,N*(N-1)/2,w[ban].ans[jj]+w[ban].ans[ll])==-1) { qq=1; break; }
}
if(qq==0)
ban++;
}
}
//字典序输出开始
for(int i=1;i<ban;i++)
w[i].wu=0;
kk=ban;
zdx(1);
fclose(stdin);
fclose(stdout);
return 0;
}
附上标准程序(我没看懂):
满分做法显然不可能讨论每个位置所有的情况,肯定是有规律的,现将这n*(n-1)/2个数排序,假设N个数组成的排列是a1,a2,......,aN,并且a1≤a2≤......≤aN.那么最小的那个和肯定是a1 + a2,次小的那个和肯定是a1 + a3,第三小的就不好确定了,如果能把a2 + a3给求出来,那么就能把a1,a2,a3给解出来,所以枚举a2+a3是哪一个,把a1+a2,a2+a3,a1+a3给求出来后从原数组中删掉,那么剩下的最小的数就是a1+a4,a4可以解出来,再把a2,a3与a4相加,把得到的数给删掉,再对a5进行同样的操作,就能得到整个序列了,所以枚举a2+a3的位置,并check一下就好了.
check的时候要先判断这个ai+a1在原数组中存不存在,是否都已经被占用了,要判断好所有的情况才行.
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n, a[100010], cnt, tot, ans[10010][310], val[310];
bool vis[100010];
void solve(int x)
{
memset(vis, 0, sizeof(vis));
int temp = (a[1] + a[2] + a[x]) / 2;
val[3] = temp - a[1];
val[2] = temp - a[2];
val[1] = temp - a[x];
vis[1] = vis[2] = vis[x] = 1;
int cur = 3;
for (int i = 4; i <= n; i++)
{
while (cur <= cnt && vis[cur])
cur++;
if (cur > cnt)
return;
val[i] = a[cur] - val[1];
vis[cur] = 1;
for (int j = 2; j < i; j++)
{
if (val[j] > val[i])
return;
int v = val[j] + val[i];
int pos = lower_bound(a + 1, a + cnt + 1, v) - a;
if (a[pos] != v)
return;
int pos2 = pos;
while (pos2 && a[pos2] == a[pos])
pos2--;
pos2++;
while (pos2 <= cnt && a[pos2] == a[pos] && vis[pos2])
pos2++;
if (a[pos2] != a[pos] || vis[pos2])
return;
vis[pos2] = 1;
}
}
++tot;
for (int i = 1; i <= n; i++)
ans[tot][i] = val[i];
}
int main()
{
scanf("%d", &n);
cnt = n * (n - 1) / 2;
for (int i = 1; i <= cnt; i++)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + cnt);
for (int i = 3; i <= cnt; )
{
solve(i);
int j = i;
while (j <= cnt && a[j] == a[i])
j++;
i = j;
}
printf("%d\n", tot);
for (int i = 1; i <= tot; i++)
{
for (int j = 1; j <= n; j++)
printf("%d ", ans[i][j]);
printf("\n");
}
return 0;
}
最后再附赠sort排序奥秘:
sort默认从小到大排,仅仅好像对数组有用,sort(x+n,x+m),排序为从x[n]到x[m-1] ,即左闭右开。
如果想要从大到小排或更复杂
要
bool cmp(const AT &x,const AT &y)
{return x.shu<y.shu;
}
对了,sort头文件:#include<algorithm>
再附赠网址:https://baike.baidu.com/item/sort%E5%87%BD%E6%95%B0/11042699?fr=aladdin
P104 zhx 街灯
第 6 页 共 6 页
街灯
【问题描述】
你是能看到第三题的 friends 呢。
——aoao
街上的街灯亮起,指引向着远方的路。每个街灯上都有一个数,每次询问,
第𝑙个街灯到第𝑠个街灯上的数模𝑝等于𝑣的有几个。
【输入格式】
第一行两个数𝑂,𝑁,代表街灯的个数和询问的个数。
一行𝑂个数,代表街灯上的数。
接下来𝑁行,每行四个数𝑙,𝑠,𝑝,𝑣代表一组询问。
【输出格式】
对于每次询问,输出一行代表答案。
【样例输入】
5 2
1 5 2 3 7
1 3 2 1
2 5 3 0
【样例输出】
2
1
【数据规模与约定】
30%的数据,1 ≤ 𝑂,𝑁 ≤ 10 3 。
另外30%的数据,每次询问的𝑝一样。
对于100%的数据,1 ≤ 𝑂,𝑁 ≤ 10 5 ,街灯上的数不超过10 4 ,1 ≤ 𝑝 ≤ 10 9 。
最后一个题,没啥可说,二十分钟做完,过了三个点。然后我就没再看。
我的代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int N,M,w[10002],l,r,p,v,ww;
int main(){
//freopen("light.in","r",stdin);
//freopen("light.out","w",stdout);
scanf("%d%d",&N,&M);
for(int i=1;i<=N;i++)
scanf("%d",&w[i]);
for(int i=1;i<=M;i++)
{scanf("%d%d%d%d",&l,&r,&p,&v);
ww=0;
for(int j=l;j<=r;j++)
if(w[j]%p==v)
ww++;
printf("%d\n",ww);
}
fclose(stdin);
fclose(stdout);
return 0;
}
没标注输出。





浙公网安备 33010602011771号