↑↓CH2's Space


  • 首页

  • 标签

  • 分类

  • 归档

  • 日程表

Codeforces Round #554 (Div. 2) D、E题解

发表于 2019-04-27 | 分类于 题解 |

上紫~

其实感觉这次比赛,最坑的还是C题……虽然本菜最后乱猜瞎搞,算是搞出来了,也能大概说明理由,但Acoder巨佬告诉我们有结论可以用,所以还是等他的题解吧……
但是D和E其实完全都是可做的,难度并不高,而在比赛时做出来的人很少。考场上,真正被题目打倒的,有时还没被吓死的人多。

D:给一个n(n≤1000),得到所有由n对匹配括号构成的字符串(显然每个字符串长度为2n),设一个字典树仅包含所有这样的字符串,并设定一个集合,这个集合包含这棵字典树的若干条边,且集合中任意两条边没有公共点。求这个集合中元素数量的最大值。

这道题题意描述相当绕……观察一下题目下方的字典树,从底向上看,对相邻的两层边,显然取底部的一条边一定不会比取它上方的一条边劣,而一旦取了下层的某条边,其上方相连的边就不能取了,因为这两条边共用了一个节点。所以不妨取最下方一层边,倒数第三层边,倒数第五层……又由于同一层的边也不能共用节点,所以实际能取的边数相当于倒数第二层节点,倒数第四层节点,倒数第六层节点……的数量和。

那么如何求每一层节点的数量呢?其实,看到括号匹配,我们一般会想到先把n对括号匹配形成字符串的总数量求出来。由于n不超过1000,以及状态可以很容易地表示,考虑$n^2$的dp。由于每个位置选择括号的方案数只与之前两种括号各自的数量和可行的方案数有关,而与其排列无关,设f[i][j]表示前i个括号中有j个左括号的方案数(注意j的范围:显然j的数量不能多于n,但也不能少于i/2+i%2,否则当前左括号数量少于右括号数量,即当前方案非法。这样可以确保非法字符串方案数为0),分当前括号是左括号还是右括号两种情况进行转移。由于确保非法的方案数为0,所以不会出现非法方案转移出合法方案的bug。

转移方程:
$f[i][j]=f[i-1][j-1]+f[i-1][j],(i//2+i\%2≤j≤n)$(部分杨辉三角???)
也不需要啥优化了……很简单的方程。
所以我们发现,在求总方案数时,字典树每层节点数量也顺便求出来了!因为第i层节点数就是前i个括号的总方案数,也就是f[i][1]~f[i][n]的和。按照最开始的贪心策略,随便统计一下就解决啦。

考场上这道题只有360人做出来,但回过头看这道题,其实难度并不大,只是一个很明显的贪心和很简单的dp就出来了。估计大家都被C题搞蒙了,然后看到D题绕来绕去的描述还有可怕的字典树,直接吓跑了……

代码也非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[4020][4020];
const ll p=1000000007;
int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n;cin>>n;
f[0][0]=1;
for(int i=1;i<=n*2;i+=1){
for(int j=i/2+i%2;j<=n;j+=1){
f[i][j]=f[i-1][j-1]+f[i-1][j];
f[i][j]%=p;
}
}

ll ans=0;
for(int i=n*2-1;i>=1;i-=2){
for(int j=1;j<=n;j+=1){
ans+=f[i][j];
ans%=p;
}
}
cout<<ans<<endl;
return 0;
}

E题就更惨了……考场上只有34人做出来(当然本菜当时也没做出来,考完回头一看才明白……)。题目描述同样很绕:有一组数a,定义b[i]=min(a[i],a[i+1]),c[i]=max(a[i],a[i+1]),然后打乱b和c的顺序得到b’和c’,但是对每个i,b[i]和c[i]的相对位置不变,即:如果b[i]->b’[j],那么c[i]->c’[j] (原题目描述还要绕,搞了一个排列来表示对应关系,但实际就是这个意思)。现在给了b’和c’,求原来的a是否存在,如果存在,求出任意一个可能的a。

根据题目描述,b[i]=min(a[p],a[p+1]),c[i]=max(a[p],a[p+1]),也就意味着b[i]和c[i]在原数组相邻,但先后顺序未知。这样得到n-1组相邻关系。那么如何用这样的相邻关系得到原数组呢?如果把每一个数字当做一个节点,在相邻的两个数字之间连一条边,可以发现,从某个点出发,经过所有的边各一次,遍历到的点按遍历顺序排列(也就是求欧拉路径),就是原来的数组a。由于数字比较大($≤10^9$),所以先离散化,然后就是敲板子了。

如果还不理解……可以看一下洛谷P1341……几乎一样的题,但那玩意还要保证字典序最小……还只是个蓝牌子……

要注意本题可能有大量自环,如果板子不太好,时间复杂度可能直接退化。本菜的板子就有点糟,被卡T了两次,改了半天……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>
#define maxn 205000
#define maxm 405000
using namespace std;

struct ljb{
int firstEdge[maxn];
struct edgeNode{
int x,v,next;
}edge[maxm*2];
int deg[maxn],went[maxm],m,n,findz,EulerTag;

ljb(){
memset(this,0,sizeof(*this));
}

inline void append(int op,int ed,int va){
++m,++deg[op];
edge[m].v=va,edge[m].x=ed;
edge[m].next=firstEdge[op];
firstEdge[op]=m;
}

inline void EulerDfs(const int&now,vector<int>&ans){
for(int q=firstEdge[now];q;q=firstEdge[now]){
if(!went[edge[q].v]){
went[edge[q].v]=1;
++EulerTag;
firstEdge[now]=edge[q].next;
EulerDfs(edge[q].x,ans);

}
else firstEdge[now]=edge[q].next;

}
ans.push_back(now);
}
inline vector<int>EulerCircle(){
int oddDeg=0;
for(int i=1;i<=n;++i){
if(deg[i]&1)++oddDeg;
}
EulerTag=0;vector<int>ans;
if(oddDeg==2){
int f=0;
for(int i=1;i<=n;++i){
if(deg[i]&1){f=i;break;}
}
EulerDfs(f,ans);
}
else if(!oddDeg){
EulerDfs(n,ans);
}
if(EulerTag<m/2)ans.clear();
return ans;
}
}graph;

int dt[101000],b[101000],c[101000];
int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n;
cin>>n;
for(int i=1;i<=n-1;i+=1){
cin>>b[i];
}
for(int i=1;i<=n-1;i+=1){
cin>>c[i];
}
map<int,int>lsh;
int l=0;
for(int i=1;i<=n-1;i+=1){
if(b[i]>c[i]){cout<<-1<<endl;return 0;}
if(lsh.find(b[i])==lsh.end()){lsh[b[i]]=++l;dt[l]=b[i];}
if(lsh.find(c[i])==lsh.end()){lsh[c[i]]=++l;dt[l]=c[i];}
graph.append(lsh[b[i]],lsh[c[i]],i);
graph.append(lsh[c[i]],lsh[b[i]],i);
}
graph.n=l;
vector<int>ans=graph.EulerCircle();
if(ans.size()){
for(auto&k:ans){
cout<<dt[k]<<' ';
}
cout<<endl;
}
else cout<<-1<<endl;
return 0;
}

总而言之,卡B卡C,降智打击。所以卡题的时候,一定要敢跳,不然等着冷冰冰……(血泪教训QAQ)
以上。

Codeforces Global Round 2 D、E题解

发表于 2019-04-26 | 分类于 题解 |

D:音乐题,如果学过吉他,可能比较容易理解题意:
吉他第i弦的第j品的音调为i弦的空弦音+j,给n(≤1e5)个弦的空弦音,有q(≤1e5)个询问,每次求所有弦第l品到第r品(0≤l≤r≤1e18)共能表示多少个音。
首先显然要把弦按空弦音升序排序。每次覆盖时,答案显然只和(r-l+1)有关。所以不妨把l统一设成0,对每根弦每次覆盖到的区间是[0,r-l]。这样就可以把每根弦的空弦音放在一根数轴上,对每个节点覆盖长为(r-l+1)的区间,答案就是被覆盖到的区间总长度。图为样例一的第二问,区间长度为(2-0+1)=3。
线段
我们可以发现,如果某个区间长度大于相邻两个点的距离,那么区间大于该距离的部分一定会被后一个区间“截掉”(如图中的1和3距离为2,小于当前区间长度3,所以以1开始的区间[1,3]的部分被后面的[3,5]覆盖;类似的,区间[3,5]又有一部分被[4,6]覆盖等)。只有长度小于相邻两个点距离的区间得到完整的保留。由于被截掉的区间相当于只剩下相邻两点距离的长度,因此所有部分被截掉的区间长度和就是所有这些相邻点距离的和。因此可以分别统计“部分被截断的区间总长度”和“全部保留的区间总长度”。只要找到所有长度小于询问区间长的相邻点距离并求和,再加上剩余未被截断的区间总长度就是答案。所以可以预处理相邻点的距离,并将它们从小到大排序,求出前缀和,就可以按每个询问的区间长二分查找,快速求出符合要求的区间长度和。未被截断的区间更好处理,求出总数量后乘区间长度即可。
(所以从代码上看就是各种排序+差分+前缀和……)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;

ull s[202000],dta[202000],exadz[202000];
int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
int n;cin>>n;
for(int i=1;i<=n;i+=1){
cin>>s[i];
}
sort(s+1,s+n+1);
for(int i=2;i<=n;i+=1){
dta[i-1]=s[i]-s[i-1];//先差分求相邻点距离
}
sort(dta+1,dta+n);//排序
for(int i=1;i<=n;i+=1){
exadz[i]=exadz[i-1]+dta[i];//求前缀和
}
int q;cin>>q;
for(int i=1;i<=q;i+=1){
ull l,r;cin>>l>>r;
ull d=r-l+1;
int t=lower_bound(dta+1,dta+n,d)-dta;
ull ans=(n-t+1)*d;
cout<<ans+exadz[t-1]<<' ';
}
return 0;
}

另一种思路是离线处理,将询问按区间长从小到大排序,再利用单调性求出答案,同样要各种差分排序……在此不再赘述,可以参考zyp巨佬的代码。


E:贪心乱搞题。有a[i]个长度为$2^i$的火柴,问这些火柴总共能构成多少个三角形?

显然,每个三角形必须由两根长度为$2^i$和一根长度为$2^j$的火柴构成,其中j<=i。
这句话显而易见,但只要意识到这句话的正确性,答案也就呼之欲出了!
考虑哪些火柴是不能用来组成三角形的:对于每一种长度,如果它有奇数个火柴,且没有两根长度相等且大于等于它的长度的火柴来跟他匹配,那么这根火柴相当于作废。
所以按长度从大到小遍历各种火柴,先不急着拼出三角形,而是让火柴成对匹配,实时统计长度大于等于i的成对火柴数,如果某根火柴不能成对,即当前长度的火柴数量为奇数,那么需要消耗一组长度大于等于它的成对火柴来跟他形成三角形。如果当前没有成对火柴,却有单独的火柴,那么这一根单独的火柴就作废了。
这样扫一遍,可能还会剩下一堆成对火柴没有拼成三角形,但显然可以拆掉若干对长度较小的火柴来和长度较大的成对火柴拼成三角形。所以直接把没有作废的火柴数量整除3就得到答案了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[402000],b[402000];

int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
int n;cin>>n;
ll w=0,s=0;
for(int i=1;i<=n;i+=1){
cin>>a[i];
s+=a[i];
}
int t=0,bd=0;
for(int i=n;i>=1;--i){
t+=a[i]/2;
if(a[i]%2){
if(!t)++bd;
else --t;
}
}
cout<<(s-bd)/3<<endl;
return 0;
}

以上。

Codeforces Round #551 (Div. 2) D、E题解

发表于 2019-04-26 | 分类于 题解 |

D:一棵树有n(2≤n≤3e5)个节点,其中有k个叶子节点,叶子节点的值是1~k的任意一个排列(自己定),非叶子节点的值是它所有儿子的最大值或者最小值。求根节点(1号)可以拥有的最大值。
自底向上树形dp。如果一个节点为max节点,那么不妨只保留一个儿子作为结果的最大值,而令其他儿子的子孙叶子节点尽可能小。这相当于删去了其他儿子,并把这个节点的度变为1。显然,度为1的max节点和min节点是等价的。所以经过若干次操作,这棵树只剩下min节点,所以答案就是剩下的叶子节点的最小值,即(k-w+1),其中w为剩下的叶子节点数。(1,2,…,k-w在max节点中被”比下去”了,也就相当于被删除了)所以,为了让w尽量大,我们总是希望删除尽量多的叶子节点。因此,统计每个节点的子孙叶子节点数量,max节点只保留拥有最少的子孙叶子节点的儿子,同时还要累记被删掉的节点数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
using namespace std;
struct node{
int fa,sh;//,nleaf;
vector<int>son;
}st[400000];
int k=1,a[400000];
void dfs(const int&now){
if(st[now].son.size()){
int minm=INT_MAX,sum=0;
for(auto&x:st[now].son){
dfs(x);
minm=min(minm,st[x].sh);
sum+=st[x].sh;
}
if(a[now]){
st[now].sh=minm;
k+=sum-minm;
}
else st[now].sh=sum;
}
else{
st[now].sh=1;
}
}

int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i+=1){
cin>>a[i];
}
for(int i=2;i<=n;i+=1){
int f;cin>>f;
st[i].fa=f;
st[f].son.push_back(i);
}
dfs(1);
cout<<k<<endl;
return 0;
}

E题难受了……本来可以做出来,结果前面太磨蹭,然后还搞错了结束时间,结果莫名其妙提前放弃了……最后5分钟还在纳闷怎么还没结束……
不过赛后还是AC了(彩笔人生第一道交互题)。思路很简单:如果某个矩形包含了蛇的头或者蛇的尾中的一个,那么回答是奇数;否则是偶数。由于有2019次询问机会,而n不超过1000,因此可以询问n个1×n的矩形和n个n×1的矩形,找奇数的回答,就是头或者尾所在的坐标。如果头和尾既不在同一行也不在同一列,那么头和尾的坐标也都确定下来了,对4种可能的点分别询问一下即可。如果头和尾在同一行,那么通过之前的询问只能知道头和尾分别在哪一列,因此还要用二分查找确定在它们在哪一行。头和尾在同一列同理。
本题虽然思路简单,但实现比较繁琐,因此AC代码不贴了。
以上。

Codeforces Round #550 (Div.3) G题题解

发表于 2019-04-26 | 分类于 题解 |

人生第一个独立完成的CF压轴题?

(然而是Div3+赛后才做出来,彩笔)

首先是dp思路(一看就是dp,不多解释):考虑到每个点要么被放到升序队列,要么放到降序队列,那么只要让当前升序队列最后一个数尽量小,降序队列最后一个数尽量大就可以了。

f[i][1]表示把a[i]放到升序队列,此时降序队列队尾最大值(升序队列队尾最小值就是a[i],不用记录);f[i][2]表示把a[i]放到降序队列,此时升序队列队尾最小值。(同理降序队列队尾最小值就是a[i])

状态出来了,转移也很明显:当前元素a[i]如果能放到升序队列,那么它必然比升序队列队尾元素大。如果a[i-1]放在升序队列,此时降序队列队尾就是f[i-1][1];如果a[i-1]放在降序队列,此时升序队列队尾是f[i-1][2]。每种情况在满足a[i]大于升序队列队尾的情况下,f[i][1]取两个降序队列队尾的最大值。
所以得到转移方程:

1
f[i][1]=max(f[i-1][1](a[i]>a[i-1]),a[i-1](a[i]>f[i-1][2]))

转移的时候顺便把”f[i][1]是由哪个队列的a[i-1]转移而来”记录下来,便于统计答案。
f[i][2]类似处理,请自行推导。

AC代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int a[400000],f[400000][3],lst[400000][3];
int main (/*int argc, char const* argv[]*/){
std::ios::sync_with_stdio(false);
int n;cin>>n;
for(int i=1;i<=n;i+=1){
cin>>a[i];
}
f[1][1]=INF,f[1][2]=-1;//f[i][1]:a[i]放在单调增队列末尾,此时单调减队列的最大值;f[i][2]:a[i]放在单调减队列末尾,此时单调增队列的最小值
for(int i=2;i<=n;i+=1){
f[i][1]=-1,f[i][2]=INF;
if(a[i]>a[i-1]){
if(f[i-1][1]>f[i][1]){
f[i][1]=f[i-1][1];
lst[i][1]=1;
}
}
if(a[i]>f[i-1][2]){
if(a[i-1]>f[i][1]){
f[i][1]=a[i-1];
lst[i][1]=2;
}
}
if(a[i]<a[i-1]){
if(f[i-1][2]<f[i][2]){
f[i][2]=f[i-1][2];
lst[i][2]=2;
}
}
if(a[i]<f[i-1][1]){
if(a[i-1]<f[i][2]){
f[i][2]=a[i-1];
lst[i][2]=1;
}
}
}
int lw=0;
if(f[n][1]!=-1){
lw=1;
}
else if(f[n][2]!=INF){
lw=2;
}
if(!lw){
cout<<"NO\n";
return 0;
}
cout<<"YES\n";
stack<int>ans;ans.push(lw);
int now=lw;
for(int i=n;i>1;--i){
ans.push(lst[i][now]);
now=lst[i][now];
}
while(!ans.empty()){
cout<<ans.top()-1<<' ';
ans.pop();
}
cout<<endl;
return 0;
}

以上。

2018焦作站现场赛E题

发表于 2019-04-26 | 分类于 题解 |

水题没人写题解,都直接上Java代码……那我写一个。

计蒜客题面:https://nanti.jisuanke.com/t/A2203
题意:如果i是完全平方数(>=4)的倍数,那么i号电阻的阻值为无穷大,否则为i。
现在有编号为1~n的集合,每个集合包含若干个电阻,编号为i的集合包含所有编号为i的约数的电阻。求一个集合,使该集合内所有电阻的并联阻值最小。
关键的来了:n<=10100


首先,对于一个电阻,如果它能改变总电阻,那么着它的阻值不是无穷大,因此它的每个质因子最多被乘了一次。所以对于每个电阻的集合,它的编号的约数中,只有1、质因数、以及若干个不同质因数的积是有效的。另外,如果一个数的约数包含了若干个质因数,那么这个数的约数也必然包含这些质因数的积。 这样,只要选出积不超过n的若干个质数,再加上公共约数1,就是要找的集合。

那么选取哪些质数呢?从让阻值尽量大的角度来讲,在质因子数量相同的情况下,应当选择尽量小的质数。以比较集合6和集合10为例,6=1×2×3,10=1×2×5,显然电阻5和(2×5=10)两个电阻都比3和(2×3=6)的阻值大,因此质因子5不如质因子3优。另外,由于要选的集合编号不超过n,在质因子数量相同的情况下,如果能选较大的质因子,必然能选编号较小的质因子。因此选质因子的策略为:在质因子的积不超过n的前提下,尽量选取最小的质因子,即依次选取2,3,5,7……

然后就是计算总电阻。题目给了电阻公式,此处R1,R2,R3,R4……Rk依次等于1,2,3,5……(2×3×5×……×p),其中p为最大的质因子。

上下同时乘以Rk,得到:R=Rk/(1+2+3+5+……+p+2×3+2×5+3×5+……+2×3×5+……+Rk)
分母中,对于每一个含有p的项,提取p,得:p×(1+2+3+5+……+2×3+2×5+3×5+……)+(1+2+3+5+……+2×3+2×5+3×5……)=(p+1)×(1+2+3+5+……+2×3+2×5+3×5+……)
观察发现,(p+1)的系数是不取p的情况下的分母值。那么这个分母就形成了递推式,后一项是前一项的p+1倍,第一项为1。这样用递推式求出分母,约分即可。
用欧拉筛预处理出前若干项质数,它们的积至少要超过n的最大值10100,经过验证,求出前300项质数已经足够了。
然后就是py= = Java不会(而且高精度类似乎比较糟),c++……考场手打高精度???
人生苦短,我用python。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import math

m,l=300,0
prime,a=[],[]
for i in range(0,m+1):
a.append(1)
a[1]=0

for i in range(2,m+1):
if a[i]:
prime.append(i)
for j in prime:
if i*j>m:break
a[i*j]=0
if not i%j:break

T=int(input())
for o in range(0,T):
n=int(input())
h,k=1,0
for i in prime:
if h*i>n:break
h*=i
k+=1
f=[1]
for i in range(1,k+1):
f.append(f[i-1]*(prime[i-1]+1))
g=math.gcd(h,f[k])
print(h//g,end='/')
print(f[k]//g)

CSP认证划水记

发表于 2019-03-21 | 分类于 题解 |

膨胀了膨胀了……

天胡

先讲一下赛制问题。CSP认证用的是NOIP赛制,也就是说在考试时不能即时看到自己的分数,这导致一大批童鞋某些题考场上春风得意,结果莫名爆0,还不知道是怎么死的;甚至还有编译器版本问题,好多悲惨的童鞋用了gets读字符串,然后选择C++11作为提交语言,于是华丽地CE了……
getline大法吼呀(get一家退环境了泥萌真的不造吗==)
不过这样的赛制倒有一个好处:有部分分的存在。很多题目,甚至不用想到最优算法,就可以骗到相当可观的分。比如这次的E题,60分骗分算法大部分人都会做,而据某群一位超NB的大佬说,E题满分是不可做的= = 于是这次比赛,最高分就是460。
(什么?不会60分算法?那floyd总会吧?N遍dijkstra会的吧?OK,30分到手==)
然而本人还是炸了E题的一个点,不明不白的==(彩笔)

本人由于个人经历,其实还算比较适应这一赛制。和ACM相比,打这种比赛,要特别注意一些不同的事情:

  1. 千万保证不CE!一定要注意本地和评测机使用的编译器可能不一样,在Dev-C++里的编译命令中可以选择语言规范标准,最好和评测机保持一致,不要乱用危险的用法! 另外,有的时候本地测试里有一些必要的头文件没有被include,但编译器悄悄自己include并放你过了,你自己不知道;但评测机并不会这么宽松,直接CE送上天!一定要千万注意!强烈推荐万能头文件”bits/stdc++.h”,不劳永逸!
  2. 千万保证不MLE!在本地测试时打开任务管理器或者资源监视器,监测内存使用空间变化有没有超过题目限制,如果超出,宁可牺牲一部分分数也要开小数组! 另外,还要考虑到函数内部申请的空间以及函数递归花费的空间开销。

    程序千万条,能跑第一条。提交不规范,爆0两行泪。

    以上这两个注意点,如果处理的不好,就会面临爆0的风险,连部分分都拿不到。爆0是一定要避免的!几乎所有题,都至少可以用最原始、暴力的做法”骗”到一些分数;如果这些分都拿不到,实在非常伤。

  3. 在把能拿到的分都拿到之前,不要为思考某道题的满分算法花费太多时间,这和ACM赛制有很大不同。 很多时候,AC一道题花费的时间,够骗起码两题的60分算法了,反而得不偿失。 还是那句话:骗分。

    由此可见,在这种赛制下,做题的规划全都是按照部分分进行的。高分并不一定意味着能AC多少题,但一定能“骗”到尽量多的分!

  4. 本地测试时充分考虑算法可能存在的漏洞,多构造几种数据,特别是比较特殊的数据(比如n=1,图的自环等等)。没有即时评测,谁也不知道最后会发生什么。比如本次比赛,我的D题写完后虽然通过了样例,但总感觉可能不对劲,后来构造了多组数据,果然找出了bug。所以,多测试,多对拍,永远不亏。(对拍是指把暴力骗分程序和试图写满分的程序用构造的数据对照测试,从而检测试图写满分的程序的正确性)

  5. 经验表明,难读懂的题其实往往比较好做,因为难点在阅读理解上(???);而难题多半是题干一目了然的那种。比如这次的C和E。


题解

A题……裸送?其实比起直接cout,个人认为还是分类讨论一下比较好。(贴吧上还真看到有老哥死在这道题上,都是赛制的锅)

B题就比较快乐了,因为数据结构课刚刚讲过中缀表达式的求解,那个还是带括号的= = 连x和*都有考场老师的提醒,应该问题不大= =

C题,题面很不友好,但根据原则4,这样的题实际上都比较水,果不其然= =(然而本渣还是炸了10分,菜逼)
首先所谓的RAID-5卷,讲了半天,就是给n+1块磁盘,来保存n块磁盘容量的数据,这样可以通过异或运算,可以在只坏掉一块硬盘的情况下恢复该硬盘的数据;但坏两块以上就不能恢复了。不过即使如此,有些数据没有存在坏掉的硬盘上,还是能找回来的。
所以第一步:统计磁盘数量,如果只坏了一块硬盘,那就用别的硬盘异或运算修好,字符串和数字互相转换而已,应该没有难度;两块以上硬盘坏掉就别管了。
然后就是找数据块了。要找块先找条带,条带编号公式给了,在条带中找块也很简单;关键是确定条带的位置。根据题干描述+观察图片样例,在每(n*n)个空间中,实际保存数据的条带有(n*(n-1))个,备份用的条带按矩阵的副对角线排列。如果把这(n*n)个条带空间称为一“区”,那么条带的排列就是按照这样的若干个“区”循环进行的:

1
2
3
4
0   1   2   P  
4 5 P 3
8 P 6 7
P 9 10 11

那就很简单了:首先根据条带的编号求出这个条带在哪个“区”,以及在这个区内的相对编号(即如上图的0,1,2,……,11,取余运算即可),那么怎么根据先对编号求出具体的坐标呢?在n很大的情况下不适合暴力枚举。观察上图,发现如果先把P忽略掉,把下面的数字三角形合并上来,得到的就是n*(n-1)的排列完成的矩形矩阵;然后就很简单了,先求出当前条带在当前区的坐标,根据坐标判断,如果在右下角的三角形中,行下标就要加一。条带的位置得到了,那对应的块的位置也就出来了,问题便迎刃而解。

当然,如果时间不够,或者题目看不下去,暂时跳过,先做D和E也是可以的。分奴做法:骗分优先。

D题……首先你不可以用gets……
dfs乱搞,随便找个没跑完的线程,因为所有的线程能跑完的必要条件是任意一个线程能跑完;然后找它目前对应请求的线程,如果不匹配,那从这个线程继续dfs下去……如果一个线程还在等待它对应的进程,但它又要被别的线程调用了,而且全程没有成功被匹配(坑点,险些凉凉),那说明这个线程彻底凉了,不用继续搜了。当所有线程都被放空,那说明问题解决。但这种dfs有可能写炸,如果妥协起见,还是n^2爆搜吧,至少60分是稳的。如果写了100分算法,结果写错了爆0,实在得不偿失= =

E题60分做法:我们注意到这些部分分的数据中,轨道发动机的数据范围都比较小。所以从轨道发动机开始反向dijkstra,对每个节点统计与轨道发动机之间的距离,保留最小的k个即可。
100分做法:别问本菜鸡。真的不会= =

以上。

NWPU春季赛,题解与反思

发表于 2019-03-21 | 分类于 题解 |

正式赛题目:https://www.luogu.org/contestnew/show/15288

兴冲冲去打校赛。结果:三题死。爆炸的理所当然,也确实反映了自己的不少问题。


上午热身赛5T,A了4个(热身赛比正式赛A的多系列==)不知为何题目一直没有放出来,目前也没有题解。(@Acoder出来写题解……)

ABD裸送,关键是C(题目名:水 题)和E。

E题是跟某位大佬讨论才做出来的。
题目描述:n个节点(<=100),找出长度为k(<=10^18)的回路,求每个节点经过的回路数量。

题目相当于求以这个节点为回路的起点,求长度为k的回路的数量。这里用到了邻接矩阵乘法:邻接矩阵的k次幂d[i][j]代表从i经过k步到达j的方案数(加法原理&&乘法原理)。矩阵快速幂即可。
注意有坑:可能有重边,需要多次累加而非一次赋值。

C题:n(<=10^5)个元素,q(<=10^5)个询问,每次求区间在[i,j]范围、数值在[a,b]范围的数的异或和。
首先想到先排序,把问题转变为新下标在[s[a],s[b]]之间,原下标在[i,j]之间的异或和。这样可能可以初始化区间异或和来简化问题。
然后呢?分块+线段树?树套树?于是跑路了。并不怎么会数据结构题。


下午的比赛,被A题彻底卡死。看到那么多人AC了,题目又偏水,一直试图做出来,一直莫名WA,换了种暴力做法继续WA。考完结果告诉我数据有问题……
(彩笔不会切割线定理,究竟是谁的锅?)

但关键问题并不是一道题的得失。在赛场上,有一道题目,排名在你后面一排的都A了,而你还在大红W A大紫R E,你还能保持住良好的心态,继续思考别的题目吗?

显然,我在这点上失败了。后面几乎每看一道题,看着看着都要回来改改A题。结果导致很多本来可以做出来的题目zz了。当然菜也是一方面,不然N道题直接水过去了;但更关键的是心态问题,在紧张的ACM赛制下,心态的好坏是导致最终成败与否的最关键的临场因素。

之前还有一段小插曲:我搞错了开始时间,1点开始的比赛,我以为两点才开始,于是蜜汁失去了45min。时间的失去不是最重要的,更为要命的是对心态的打击。再加上A题没搞出来,节奏彻底乱了套。所以下面的许多题,不是脑子糊涂就是各种zz。以这样的状态,原地爆炸实在不出所料。

B题==乱搞,n年前研究过,后来做了个不太一样的题目,结果现在想的太多,凉了。

万恶的刷油漆

就是这玩意……这道题只能取长为1||宽为1,现在长和宽都随便取,完全不一样……

C题:裸送。

D题状压dp,之前搞过很多,结果考场上脑子糊涂了,在想是不是有可能最大费用最大流……
另外,现在才知道,当年写的状压非常蠢。一般的状压,是可以按自然数顺序枚举状态的,完全不需要按二进制中1的个数排序……简直自找麻烦。不知道有没有人和我当时一样==

E题:组合数题。高中水题,现在都不会了(被费马小定理吓跑了,然而只是用来求逆元的==)

F题:树形dp,想到树上距离不构成三角形只有链以及”正难则反”基本就稳了,但是考场上光想着LCA以及LCA完了怎么办……如果当时能细心思考一下,其实是可做的。

G题:裸送。

H题:优秀的dp题,但数据规模吓人,连n^2都不敢写了……正确的顺序是先写暴力dp,再考虑优化。

I1&I2:spfa乱搞。建图啥的不太想研究。但要是不会优化spfa,就算它死了,还是能搞你。

SPFA死了
(from洛谷大佬Chtholly)

其实仔细想想,spfa的过程中,对当前节点的某个方向,直接相连的距离为0并入队首,以及拐弯相连的距离为1并入队尾,实际上和建图的方法有异曲同工之妙。首先更新一个节点相连的所有点,要么两者距离为0,要么距离为1,不可能有更多了。当完成距离为0的点的更新,并开始更新距离为1的点,实际上相当于完成了std算法蛇皮建图对一个线段节点的一次更新,并拓展到相邻联通的线段节点。
另外注意到,当任意节点距离只能为1时,spfa和bfs是相同的,因为没有已经出队的节点会被重新入队并松弛。当然这并不代表spfa活了==也许可以看作允许一点容错的bfs吧。

J题:故事写的不错,然而这次轮到本菜身败名裂了==

穹链剖分

(From 洛谷某大佬,id忘了==)
那就剖吧。不出意料的无人生还==

这次比赛,题出的好(劝 退 树 剖),难度适中(两 题 进 队),覆盖知识点广(花 样 D P),又比较符合实际背景(Kokoa 心 爱),解法也比较自然(蛇 皮 建 图),给出题人点赞!

Kokoa???

(from bilibili,制作组竟然搞错了心爱酱的名字?)

以上纯属调侃,勿认真。

该炸的炸完了,那么,然后呢?

实话实说,出题人确实下了一番功夫。也很感谢这次比赛,暴露了我隐藏的不少问题。

1、心态。放下那道题,或者交给信赖的队友。
2、时间策略。首先对题目难度的正确估计,判断是否可做;然后在此之上合理分配时间。显然本菜高估了许多题的难度系数,导致时间分配非常不合理。
3、对算法时间复杂度的熟悉。比如D题,数据范围已经透露了正解,就不要胡思乱想了。网络流的数据范围在几十到几百之间,状压dp一般不超过20。要是实在不知道复杂度,还可以用calc计算。
4、对一些方法的半知半解。比如线段树和树剖之类的算法和数据结构,还是停留在解决模板题的程度。这好比会dinic但不会建图,会dp但找不到状态,一句话:没卵用。多练题吧,唯有刷题才是王道。

总之,这次比赛敲响了警钟。是时候清醒一下了。

12
↑↓CH2

↑↓CH2

blog

17 日志
4 分类
26 标签
Links
  • ZH巨佬
  • ACoder巨佬
  • WSY巨佬
  • 刘总主席
  • 赵队NB
© 2019 ↑↓CH2
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4