小小的总结
本人的第一场XCPC系列比赛,可惜遗憾打铁了,或许是对组队赛不熟悉,亦或许是队友直接磨合不好
但主要原因应该是自己太菜了(
赛时过了EDJ三题,A和队友琢磨了好久,最后思路是对的,但是不知道卡在了哪里,B题另一位队友的思路也是对的,但是代码写史了,WA了以后也调不出来
汉堡放凉了也没吃滚榜的时候才吃的ww
总结下来,打印的板子几乎没用到,够到铜牌(甚至银牌)只需要思维敏捷就够了
所以加训思维,少学无用算法,或许对我这种弱队菜鸡才是最优解。
另外还要加强和队友的配合,赛时和他们通思路好几次都是思路没法对到一块,要花好长时间理解他们(或许是我脑子转不动的问题)
下面是题解,按照难度(自认)排序
签到,
用一个map记录第 秒有多少队伍报名,然后遍历map,若第 秒的报名人数 则记入答案,否则跳过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void solve () { ll n, x; cin >> n >> x; map<ll, ll> mp; ll tot = 0 ; for (int i = 1 ; i<=n; i++) { ll v; cin >> v; mp[v]++; } for (auto [v, c]:mp) { if (c<=x) tot+=c; } cout << tot << endl; }
由于要去任意三根都要组成一个非退化凸多边形,只要三根能组成,那再加一根到这三根里面就一定可以组成一个非退化凸多边形,所以我们可以只考虑能不能组成三角形
因此对木棍长度排序,组成非退化三角形的条件是两边之和大于第三边 ,所以两个两个的遍历排序后的长度数组,用二分找出满足条件且尽可能大的边,则由于数组有序,这一段区间内的所有木棍都可以组成非退化三角形,遍历取最长区间内的元素即可,若最长区间小于3则不存在满足要求的子集
这个还是比较好想的,赛时队友直接给出了思路写了,一发A
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 void solve () { int n; cin >> n; vector<ll> arr (n+1 ) ; for (int i = 1 ; i<=n; i++) { cin >> arr[i]; } sort (arr.begin ()+1 , arr.end ()); int maxl = -1 ; PII maxi = {0 ,0 }; for (int i = 1 ; i<n; i++) { int t = arr[i]+arr[i+1 ]; int it = lower_bound (arr.begin ()+1 , arr.end (), t)-arr.begin ()-1 ; if (it-i+1 >maxl) { maxl = it-i+1 ; maxi = {i, it}; } } if (maxl>=3 ) { cout << maxl << " " ; for (int i = maxi.first; i <= maxi.second; i++) { cout << arr[i] << " " ; } } else { cout << 0 ; } cout << endl; }
由于占领不同堡垒带来的收益是一样的,显然我们应该贪心的占领代价小的堡垒
所以需要对堡垒进行排序,枚举当前堡垒能不能在 秒时买入,若由于士兵数 不够而无法买入,则我们需要等待 秒才能继续购买,直到需要等待的时间超过了总时间,提前结束,更新ans
同时要考虑到中间过程主动停止购买,以当前士兵产量过完剩余时间得到的总数是否最优,因此除了超过总时间,每次购入堡垒也需要更新 ,其中 为总秒数, 为已经过去的秒数, 为当前士兵产量, 为当前士兵数
至于为什么这道题我会认为比J难,是因为赛时思路歪了,一直以为是枚举当前秒数一直尽可能在一秒内占领尽可能多堡垒,于是没和队友对上电波,还好队友给力码完A掉了这题
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 void solve () { ll n, m; cin >> n >> m; vector<ll> arr (n+1 ) ; for (int i = 1 ; i<=n; i++) { cin >> arr[i]; } sort (arr.begin ()+1 , arr.end ()); ll cur = 1 ; ll sod = 0 ; ll ans = m*cur; ll sec = 0 ; for (int i = 1 ; i<=n; i++) { if (sec>=m) break ; if (sod>=arr[i]) { sod-=arr[i]; cur++; ans = max (ans, sod+(m-sec)*cur); continue ; } ll need = arr[i]-sod; ll nt = ((need+cur-1 )/cur); if (sec+nt>m) { ans = max (ans, sod+(m-sec)*cur); break ; } sec+=nt; sod+=nt*cur; sod-=arr[i]; cur++; ans = max (ans, sod+(m-sec)*cur); } cout << ans << endl; }
由题意可知,每次操作会使长度为偶数的区间内,奇数下标元素值加一,偶数下标元素值减一
所以每次操作进行的仅仅是区间内相邻值(从右往左)的交换,整个序列的和不变
因此使得序列a中所有数相等的这个值一开始就可以确定: ,因此若序列和不能被长度整除,就无解
设最终达到的值是 ,可以贪心的选择最小的区间 ,若 ,我们可以尽可能的把右边的值往左边移动,否则救不回来,无解
因此我们可以记录序列a的前缀和 与当前位置应该到达的总和 ,若 ,证明此时前缀已经超过所需,但交换只能从右向左,所以多出的值没法处理,无解
接下来是有解的情况,我们要尽可能最小化步数:
设 , 就是要想该区间到达 值缺少的值
由于一次操作是在偶数区间上进行的,所以每次操作只会影响相同奇偶性下标的元素的前缀
所以奇数下标的需求和偶数下标的需求需要分开解决
举个例子:
设操作区间为[2,5],则一次操作对数组的影响是[+1,-1,+1,-1],对应的前缀和变化是[+1,0,+1,0],你会发现,受到影响的位置只有偶数下标2和4
设操作区间为[1,4],同理,对应前缀和变化是[+1,0,+1,0],但此时下标是从1开始,所以受到影响的位置只有奇数下标1和3
这就是为什么要分别统计奇数下标前缀和偶数下标前缀
则我们可以分别统计操作次数了,他们均满足 ,即有多少个奇数/偶数区间不能被先前的区间覆盖,统一进行操作而需要新开区间
A题我和另一个队友想了两个多小时,思路正确但是代码写的太长导致不知道哪里错误
到最后也没调出来,可能放弃这个题去帮忙开B题或者L题会更好一点吧
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 void solve () { ll n; cin >> n; vector<ll> arr (n+1 ) ; for (int i = 1 ; i<=n; i++) { cin >> arr[i]; } ll tot = accumulate (arr.begin ()+1 , arr.end (), 0ll ); if (tot%n) { cout << -1 << endl; return ; } ll fin = tot/n; ll ans = 0 , pre = 0 , preo = 0 , pree = 0 ; for (ll i = 1 ; i<=n; i++) { pre+=arr[i]; ll t = i*fin-pre; if (t<0 ) { cout << -1 << endl; return ; } if (i&1 ) { ans += max (0ll , t-preo); preo = t; } else { ans += max (0ll , t-pree); pree = t; } } cout << ans << endl; }
题意是给定一个操作串,0代表向右,1代表向上,2可以自己改,机器人会一直按照操作串循环前进,直到到达 ,或指明无法到达
机器人为了到达 ,要走 步,这些步中,可以分为按照操作串走 轮,再加上走操作串前缀 步,我们设为 和
我们首先扣去固定的操作,只看数位2
用前缀和记录操作串中0,1,2的个数,则 就是总固定要走的步数,把这些分别从 里减去,剩下的 就是我们要通过操作2来实现的移动,若减完了 已经是负数,就直接返回无解
接下来操作2,我们可以简单的将所有的2分为四段,分别为 :前缀2变0, :前缀2变1, :后缀2变0, :后缀2变1,后缀的意思是最后走不完一整轮操作时,剩下没有操作的后缀串,此时的 和 可以由以下公式拼凑:
枚举前缀2变0,从大到小,因为要让字典序尽可能小,其余段数可以计算而得。首先要满足够减,即若 或 小于 数,说明不符合,其次,若算出来的后缀数与前缀数拼不成最终的 和 ,也不符合
另外,当 ,也就是不需要一轮就可以结束的情况需要特判,此时需要满足前缀要完全符合 与 ,且后缀尽可能的放0
发现一种情况满足直接跳出即可,此时一定是最优解,按照此时的01数量构造字符串
队友一眼出的题,但是没写出来有点可惜
这个代码好想不好写,补题的时候想了两种思路写了快200行的史山最后推倒重构,借鉴了队里其他人的码
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 void solve () { ll n, x, y; cin >> n >> x >> y; string s; cin >> s; vector<ll> pre0 (n+1 ) , pre1 (n+1 ) , pre2 (n+1 ) ; for (int i = 0 ; i<n; i++) { pre0[i+1 ] = pre0[i]+(s[i]=='0' ?1 :0 ); pre1[i+1 ] = pre1[i]+(s[i]=='1' ?1 :0 ); pre2[i+1 ] = pre2[i]+(s[i]=='2' ?1 :0 ); } ll rnd = (x+y)/n; ll left = (x+y)%n; x-=(pre0[n]*rnd+pre0[left]); y-=(pre1[n]*rnd+pre1[left]); if (x<0 ||y<0 ) { cout << -1 << endl; return ; } bool flag = 0 ; ll pre20, pre21, suf20, suf21; for (pre20 = pre2[left]; pre20>=0 ; pre20--) { pre21 = pre2[left] - pre20; if (rnd==0 ) { if (pre20!=x||pre21!=y)continue ; suf20 = pre2[n]-pre2[left]; suf21 = 0 ; flag = 1 ; break ; } else { if (x<pre20*(rnd+1 )||y<pre21*(rnd+1 )) continue ; suf20 = (x-pre20*(rnd+1 ))/rnd; suf21 = (y-pre21*(rnd+1 ))/rnd; if (suf20<0 ||suf21<0 ) continue ; if (x!=(rnd+1 )*pre20+rnd*suf20||y!=(rnd+1 )*pre21+rnd*suf21) continue ; flag = 1 ; break ; } } if (!flag) { cout << -1 << endl; return ; } int idx = 0 ; for (int i = 0 ; i<n; i++) { if (s[i]=='2' ) { idx++; if (idx>=1 &&idx<=pre20) cout << '0' ; else if (idx>pre20&&idx<=pre20+pre21) cout << '1' ; else if (idx>pre21&&idx<=pre20+pre21+suf20) cout << '0' ; else if (idx>suf20&&idx<=pre20+pre21+suf20+suf21) cout << '1' ; } else { cout << s[i]; } } cout << endl; }
Ad-hoc/构造
很奇妙的构造,具体意思是在 行与 列的网格中,除了第一行与第n+1行无障碍,其余的满足
从 开始,构造一种移动串能到达n+1行,若某次移动碰到障碍,则这次移动无效,直接到下一个移动
Easy版要求移动次数不超过 ,Hard版不超过
这道题其实上只要把所有可能的情况都移动一遍就行,首先考虑如下形状:
1 2 3 4 5 0000 1000 0100 0001 0000
由于第一行和最后一行无障碍,则行障碍肯定比列少一个,一定有一列是空的
我们可以尽可能的贴着障碍向右移动,直到碰到一个洞可以钻下去,可以构造RDLD ,这里往左移动是为了避免正下方有障碍,如:
1 2 3 4 5 0000 1000 0010 0100 0000
接下来考虑第二种形状:
1 2 3 4 5 0000 0001 0100 1000 0000
这种情况下,从 直接捅下去会被两面障碍夹击,RDLD会完全卡住,需要向上走,
依旧贴着障碍向右移动,直到碰到一个洞可以钻下去,可以构造URRD ,这种构造可以保证找到一个洞钻下去,钻下去以后需要继续移动,我们考虑继续贴着障碍移动,首先下去以后尽可能向左走L直到碰到障碍,接下来构造DL 贴着障碍下侧向左移动,一次即可,因为障碍在上面,下面不会再有障碍了,可以一直D移动到 行
综上,移动方案为RDLD*n+URRD*n+n*L+DL+nD,这是 次移动,多了两次
另外发现,对于n列,我们发现只需要进行URRD 次就可以到达结尾,这样可以减少四次,一共移动 次
后面上课补题一节课想出了easy并压到了
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 void solve () { int n; cin >> n; cout << 10 *n-2 <<endl; string ans; for (int i = 1 ; i<=n; i++) { ans+="RDLD" ; } for (int i = 1 ; i<n; i++) { ans+="URRD" ; } for (int i = 1 ; i<=n; i++) { ans+="L" ; } ans+="DL" ; for (int i = 1 ; i<=n; i++) { ans+="D" ; } cout << ans << endl; }
首先理解平衡括号串和回文括号串:
平衡括号串:由一对括号括起来的括号串
回文括号串:满足回文的括号串,即类似于(S(
和)S)的串
显然一个括号串不能同时满足这两个性质,所以题目的平衡回文括号串定义为平衡括号串内部是回文括号串
即满足 且~ 满足回文,如(())(),被两个括号括起来的是一个回文括号串
我们考虑同时向头尾插入相同长度的括号串,为了满足回文与平衡,只有下面两种情况:头插()尾插)(或头插)(尾插()
因为如果插入((那尾部也得插入((,不满足平衡性质,右括号同理
考虑区间DP,dp[l][r]表示区间 到 之间最长合法子串的长度,则有如下转移:
+ 删 除 左 右 端 点 头 尾 子 串 因此基本思路是:
寻找最大的平衡串:找最靠外的一对()
若该串长度小于4,则只能是())或((),特判输出2
否则区间DP转移
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 void solve () { int n; cin >> n; string s; cin >> s; s = " " +s; vector<vector<ll>> dp (n+1 , vector <ll>(n+1 )); ll l = -1 , r = -1 ; for (int i = 1 ; i<=n; i++) { if (s[i]=='(' &&l==-1 ) l = i; } for (int i = n; i>=1 ; i--) { if (s[i]==')' &&r==-1 ) r = i; } if (l>r||l==-1 ||r==-1 ) { cout << 0 << endl; return ; } if (r-l+1 <4 ) { cout << 2 <<endl; return ; } for (int len = 4 ; len<=n; len++) { for (int i = l+1 ; i<=r-len+1 ; i++) { int j = i+len-1 ; dp[i][j] = max ({dp[i][j], dp[i+1 ][j], dp[i][j-1 ]}); if (s[i]=='(' &&s[i+1 ]==')' &&s[j-1 ]==')' &&s[j]=='(' ) { dp[i][j] = dp[i+2 ][j-2 ]+4 ; } if (s[i]==')' &&s[i+1 ]=='(' &&s[j-1 ]=='(' &&s[j]==')' ) { dp[i][j] = dp[i+2 ][j-2 ]+4 ; } } } cout << dp[l+1 ][r-1 ]+2 << endl; }