AT专项训练 1
等我到AK AT abc 就差不多可以停更了,但是现在还是太菜了。
注:本篇记录的大多都是作者没做出来的题,快来嘲笑他!!!
Atcoder
非常多的思维题。
Educational DP Contest
E - Mod Sigma Problem
赛时没切,补题时看了好久,发现我模运算根本不会,非常糟糕,现在会了。
负数变正数有 \(((x)\bmod k+\bmod k)\bmod k\),我们将暴力式子拆开,分情况讨论,前缀和为负数的需要加mod。
E - Knapsack 2
我是唐完了,我就学了个不知所以然的01背包就随便了,但遇到这题还是不会,可以发现这题重量非常大,所以不能枚举重量了,此时我们枚举价格,找到最大的满足重量的价值,就是01背包反了过来。
总结:看数据范围做题有奇效。
F - LCS
如你所见,就是最长上升子序列,这篇题解 让我自愧不如,所以我不写了,记着箭头表示路径。
I - Coins
好像是期望dp??设状态为 \(f[i][j]\) 为选前 \(i\) 个数 \(j\) 个正面朝上的概率,直接转移 \(f[i][j]=f[i-1][j-1]*a[i]+f[i-1][j]*(1-a[i])\),注意这是全部正面 \(j\) 的总和,最后判断即可。
M - Candies
还是要打暴力啊,但是我连暴力都看不出 /(ㄒoㄒ)/~~ 算了直接说吧,设 \(f[i][j]\) 为选前 \(i\) 个数用了 \(j\) 个数的方案数,然后得到该式子:
然后发现其实后面的式子可以前缀和优化,每次前缀和计算 \(i-1\) 的结果。
总结:记得先打暴力啊!!!
U - Grouping
因为我没看到数据范围,所以我不会很正常吧,其实看到后也就只能状压了,我们先预处理每种组的情况的权值,然后再枚举一遍,这回枚举子集来统计最大权值,全集的最大权值为子集的最大权值加上补集的预处理权值。
点击查看代码
#include <bits/stdc++.h>
#define re register
const int N=1e6+1e5;
#define int long long
#define ls p<<1
#define rs p<<1|1
const int inf=1e9;
const int mod=1e9+7;
using namespace std;
int n;
int a[20][20];
int v[1<<18];
int f[1<<18];
signed main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=(1<<n)-1;i++){
for(int x=0;x<n;x++){
if((i>>x)&1){
for(int y=x+1;y<n;y++){
if((i>>y)&1){
v[i]+=a[x][y];
}
}
}
}
f[i]=v[i];
}
for(int i=1;i<=(1<<n)-1;i++){
for(int j=i;j>0;j=(j-1)&i){
f[i]=max(f[i],f[j]+v[i^j]);
}
}
cout<<f[(1<<n)-1];
return 0;
}
总结:希望下次优先看数据范围做题,虽然可能即使看了也没用。
X - Tower
很好的一道练习题,我缺浪费掉了,为什么我想不到按照承重01背包呢,为什么我都想到重量一定最大价值了为什么我就不知道按照承重背包呢。
D - equeue
还是要思考啊,我都是看完题没有秒掉立马看题解,这样很不好,但是这题暴力就可以,我还以为是动态规划。
枚举取前的个数与取后的个数,把负数吐出来,这样总是最优的,贪心地考虑问题。
AT_abc188
F - +1-1x2
发现这种类型的题只能搜索,那就搜索剪枝,分情况讨论。
-
\(x=y\) 找到了返回。
-
\(x>y\) 只能减,返回 \(x-y\)。
-
\(f[y]!=0\),搜索过,返回 \(f[y]\)。
-
\(f[y]=y-x\),\(+1\) 得到的。
-
\(y%2=0\),偶数搜索 \(dfs(x,y/2)\)。
-
\(y%2=1\),奇数搜素 \(dfs(x,(y+1)/2)\) 与 \(dfs(x,(y-1)/2)\)
F - Dist Max 2
看到题目要求答案最大值最小,直接就是考虑二分答案。
然后最重要的是如何判断答案是否合法呢,我们先按 \(x\) 坐标从小到大排序,然后我们双指针,我们的右指针 \(r\) 不断向后扫,直到 \(x_r-x_l\ge mid\) 说明存在不合法的可能,此时右移左指针,同时记录扫过的 \(y\) 坐标的最大值与最小值.
因为只有我们 \(x\) 坐标不合法时才开始判断 \(y\) 坐标是否合法,已经没有试错的机会,所以如果 \(y_l\) 的最大值与最小值与 \(y_r\) 的差值超过了 \(mid\) 就直接返回 \(0\)。
E - MST + 1
因为树是唯一的,所以加入一条边的话会从排序时就造成影响,且在建图时用到这条边的话就会造成影响,这有多次查询,当有多次查询而且在线不好修改时考虑离线操作,离线排序,如果用得到这条边不要连上,用原边连。
总结:当有多次查询修改操作时,考虑离线操作。
C - Jumping Takahashi
离谱的一道题,还是太菜了,暴力肯定不可以,但是考虑值域很小,所以直接枚举至于转移即可。
总结:注意看数据范围,什么范围小就可能枚举他作状态。
D - Strange Balls
要无敌了,我调了大概40分钟,我要服了,但是还是用栈模拟即可。
F - Sugar Water 2
看到第k大而且难全部求出时就要想到二分答案,当然还有最大值最小时。
我们不断肯定二分一个浓度然后判断这个浓度有几个组合比他大,我们将其中一人算出配浓度 \(x\) 时多余的糖,原式推导出:\(x-water[i]/(1-x)\),然后对这个人的多余浓度排序,然后在对另一个人求配成浓度 \(x\) 还差多少糖取名 \(less\)(上边式子取反),然后二分查找多余的糖,找到有多少个数字是大于 \(less\) 的,这就是组合数,最后统计就知道有几个数比当前浓度大了!!!
总结:计算第k大时考虑二分答案,查询答案时可以设计一个正态一个负态。
F - Negative Traveling Salesman
非常典型的题,一看数据范围明显的状压dp,这题在动态规划专题状压篇里讲过原题,我就不讲了吧,主要这题是没规定终点,而且要最小要用floyd求最短路然后再转移。
总结:以为完全一样,但题面不一样,要求最短路才可以。
F - Product Equality
这个数据范围就不是让你写 \(O(n^3)\) 暴力的,反而这是一个哈希练习题,而这题的哈希冲突的可能性很大,所以我们用大模数单哈希和双哈希分别写,其实就是个练习题。
单模哈希
#include<bits/stdc++.h>
#define pi pair<int,int>
using namespace std;
const int N=1e4+10;
#define ll long long
#define i128 __int128
//#define int long long
ll mod = 114514851121;
int n;
ll a[N];
unordered_map<ll,int> mp;
signed main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
string s;
cin>>s;
for(int j=0;j<s.size();j++){
a[i]=((__int128)a[i]*10+(s[j]-'0'))%mod;
}
mp[a[i]]++;
}
ll ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans+=mp[(__int128)a[i]*a[j]%mod];
}
}
cout<<ans;
return 0;
}
双模哈希
#include<bits/stdc++.h>
#define pi pair<int,int>
using namespace std;
const int N=1e4+10;
#define ll long long
#define i128 __int128
//#define int long long
ll mod = 1145851121;
ll mod2=1234567891;
int n;
ll a[N][3];
unordered_map<ll,int> mp;
ll get(ll a,ll b){
return a*(mod2+10)+b;
}
signed main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
string s;
cin>>s;
for(int j=0;j<s.size();j++){
a[i][0]=((__int128)a[i][0]*10+s[j]-'0')%mod;
a[i][1]=((__int128)a[i][1]*10+s[j]-'0')%mod2;
}
mp[get(a[i][0],a[i][1])]++;
}
ll ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans+=mp[get(a[i][0]*a[j][0]%mod,a[i][1]*a[j][1]%mod2)];
}
}
cout<<ans;
return 0;
}
E - Maximum Glutton
记住要学会设前i个选择,已选择j个这样的状态设计。
\(O(nXY)\) 已经很明显了吧,直接暴力写就可以但无法通过,我们想如果我们选了一个状态的 \(X\),通过计算出 \(Y\) 的大小就可以判断同样地计算答案了,但是我不会上面那个状态的设计,导致我不会写了,知道了之后就可以 \(f[i][j][k]=min(f[i][j][k],f[i-1][j][k],f[i-1][j-1][k-a[i]]+b[i])\) 就可以转移了,最后找到最大的 \(j\)。
AT_abc365
E - Xor Sigma Problem
首先可以前缀和求出区间的异或和,异或之前的前缀和就可以得到区间异或和,但是这样做的话是 \(O(n^2)\),所以需要按照位运算进行,设 \(f[i][j]\) 表示第 \(i\) 位 \(j\) 的个数,那这一位要计算答案肯定要取与这位不同的个数,即 \(ans+=g[j][bool((b[i])\&(1<<j))异或1]*(1<<j)\),统计完在增加这位这个数字的个数。
最后再减去长度为 \(1\) 的区间和。
AT_abc366
E - Manhattan Multifocal Ellipse
发现我该说如何推出来的,而不是如何做。
式子变形的时候说明了y值要小于右边的式子,而我们需要的是左边式子长度为小于右边式子,我们就要统计长度个数,累加小于右边式子的个数所以求个前缀和,我们再枚举合法的x即可。
F - Maximum Composition
需要手推式子,推完式子就确定的放的方法,然后再是二维dp,表示在选第i个,已经选j个的最大值,转移即可。
在有选择的状态设计中要想,在前i个选了前j个的状态。
总结:以后见到式子先手推。
D - Avoid K Palindrome
很离谱的一道题,非常高超的状压手段,我也就这么菜吧。
设 \(f[i][j]\) 为已经放了 \(j\) 个数,最后 \(k\) 个数状态是 \(i\) 的方案数。
我们枚举位置,再枚举状态,再枚举此位置放什么字母,如果是?
就随便转移,否则就只能规定转移,如果此时长度不为k,那就可以直接从上一个位置转移过来,否则,就判断目前状态是否是个回文串,不是回文串才可以转移。
最后答案就是 \(f[i][n]\),i是全部方案数,放心是回文串的都不会转移到这的(或者没值)。
点击查看代码,采购一个,看了半天
#include<bits/stdc++.h>
using namespace std;
const int N=(1<<10)+10;
const int mod=998244353;
#define ll long long
#define int ll
int n,kk;
string s;
int f[N][1005];
bool cmp(char x,char y){
if(x=='?'||y=='?'){
return 1;
}
return x==y;
}
bool is(int x){
for(int i=0;i<kk;i++){
if((x>>i&1)!=(x>>(kk-i-1)&1)){
return 0;
}
}
return 1;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>kk;
int m=(1<<kk)-1;
f[0][0]=1;
cin>>s;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
if(!f[j][i-1]){
continue;
}
for(int k=0;k<2;k++){
if(cmp(k+'A',s[i-1])){
int mm=((j<<1)&m)|k;
if(i<kk){
f[mm][i]+=f[j][i-1];
f[mm][i]%=mod;
}
else{
if(!is(mm)){
f[mm][i]+=f[j][i-1];
f[mm][i]%=mod;
}
}
}
}
}
}
int ans=0;
for(int i=0;i<=m;i++){
ans+=f[i][n];
ans%=mod;
}
cout<<ans;
return 0;
}
AT_abc371
E - I Hate Sigma Problems
记录这个数上一次出现的位置,那这个点的贡献就是 \((i-j)\times (n-i+1)\) 分别为可做左端点的个数与可做右端点的个数。
总结:区间贡献转化为点贡献。
E - K-th Largest Connected Components
很好的题,可惜翻译软件,而且没搓样例根本不知道是让求连通块内最大编号,而且还需要用 set 维护拥有权值,然后就没了,让不会 STL 的我怎么办呢??
总结:看题要推样例。
F - Teleporting Takahashi 2
简单却不简单的dp题,需要一些技巧,暴力带有优化,我不会优化,自动抛弃掉去求规律了。
先设 \(dp_{i,k}\) 表示到达第 \(i\) 个点用 \(k\) 步的方案数,于是有方程 \(dp_{v,k}=dp_{v-1,k-1}+\sum_{(u,v)\in M}{dp_{u,k-1}}\),循环边加额外边,可以发现 \(k\) 只于 \(k-1\) 有关于是压掉一维,但是时间上还是不可以,发现 \(dp_i\) 于 \(dp_{i-1}\) 是一个整体平移的过程,我们可以通过调整额外边的两端节点来达到自动平移的效果(而且每次移动自动实现 \(dp_{v}=dp_{v-1}\),此时 \(v\) 只是一个相对广义位置),具体如下。
for(int i=1;i->k;i++){//不知道什么情况
for(auto j:v){
int x=j.first,y=j.second;
x=((x-i+1)%n+n)%n;y=((y-i)%n+n)%n;
ls[y]=dp[y],ls[x]=dp[x];//存下,不要相互影响
}
for(auto j:v){
int x=j.first,y=j.second;
x=((x-i+1)%n+n)%n;y=((y-i)%n+n)%n;
dp[y]=(dp[y]+ls[x])%mod;
}
}
在效果是确实是实现了整体的平移,毕竟此时位置只是一个相对的位置。%%%
总结:dp题先从暴力入手,说不定就对了。
AT_abc373
D - Hidden Weights
唉,赛时被诈骗了,我直接就是写拓扑然后爆炸,其实是建双向边跑dfs就可以,咱就是说 D 题能出多难的题呢,我为什么老是想不出来呢。
总结:看数据做题。
C - Separated Lunch
非常好的题,让不看数据范围的我炸裂,其实 \(n\le20\) 就可以用爆搜或者状压dp,但是我一个都没想出来,唉真是唐诗300首啊。
总结:看数据做题。
D - Laser Marking
爆搜每个端点的情况,这题 \(n\le6\) 非常之少,不会就我喜欢贪心吧,不行,以后遇到数据范围小的我都要写暴力!!!!!!
总结:看数据做题。
E - Sensor Optimization Dilemma 2
我写了二分和背包dp,发现空间能炸死我,需要数学知识,在下无能只能看题解了。
看到要w最小值最大,可以想到用二分这个最小值判断是否符合,但是如何计算在达到w的前提下所花的钱数最小呢。
计算每个数的性价比,我们肯定想让性价比高的出现更多,所以可以枚举性价比低的剩下都是性价比高的,这样可以保证性价比高的最多。
因为 \(Ax+By\ge k+e\),e为多出的数 \(A-B\),因为AB最多有100个,所以式子变为 \(Ax-By-k\le 100e\),所以我们可以通过调整100次来找到答案的解。
总结:看数据做题,动态规划明显不行考虑贪心。
非常牛的比赛让我脑子旋转。
C - Spiral Rotation
非常离谱的一道题,首先你要读懂题才可以,而且不看样例完全看不懂,然后发现是从外向内第 \(k\) 层顺时针旋转 \(k\) 次 \(90\degree\) 好已经开始离谱了,但是非常暴力地每层真转 \(k\) 次,发现 \(4\) 次一循环就可以对 \(4\) 取个余数就可以了。
总结:读不懂题看样例推。
D - ABA
很离谱的一个题,发现回文的三个数和中间的那个数没有关系,所以对于每个位置统计左右每个字母出现次数,枚举字母,直接乘法原理,左边次数乘右边次数,好统计答案,没了,但是离谱。
总结:求区间贡献时转化为求点贡献。
E - 3 Team Division
这种题我确实没做过 虽然是标准背包,但是就我这入机脑子怎么想的出来,还是菜就多练,现在见到了以后的题也类似的向这靠。
不是一般的贪心题,也没法贪心,但没法贪心不就是动态规划了吗,所以用动态规划做,我们设状态为 \(f[i][j][k][o]\) 表示前i个人,第一个体积为j,第二个体积为k,第三个体积为o的最少花费吗,不!!!这样数组根本存不下,所以我们考虑压去一维,把第三个去掉,然后我们枚举人和体积,分别有:
最后的答案就是 \(f_{n,sum/3,sum/3}\)。
然后这题就这么暴力地做完了……
总结:放物品和价值时可以想到背包,且背包要尽量压。
F - Road Blocked
又是一道做过类似的题,和之前并查集的一样,但是我还是装作没做过吧,删边不容易更新,正难则反,考虑离线处理每次都加边,一开始先floyd求出每个点对的最短路,然后倒叙处理操作,如果是删边(加边),就更新最短路,怎么更新,枚举两个点对,如果他们的最短路经过该边就更新 \(f[j][k]=min({f[j][k],f[j][u[z]]+w[z]+f[v[z]][k],f[j][v[z]]+w[z]+f[u[z]][k]});\)。
然后最后统一输出即可。
总结:正难则反,又删边(加边,加点)等复杂操作时,又要求出,多去想想可不可以离线。
G - Road Blocked 2
虽然确实简单,但我还是不会,菜!!!
确实我做题都不愿意动手画画想想,这题却是一个动手就能做出的题。
考虑有多条最短路时,无论删哪条都不会产生影响,哪什么类型的边一定会影响最短路呢,正是不论走那条最短路都必须经过的边,你可以画一画看看是不是。
哪怎么知道最短路会走哪些路呢,你不会说你要枚举吧!!当然不可以,每条边有两个端点,我们用这个 \(dis_{1,u}+w_{u,v}+dis{n,v}==dis{1,n}\),如果此时的 w 满足条件那一定就是最短路上的了,所以需要分别从 \(1,n\) 当起点跑最短路,然后将最短路上的边建新图,那怎么知道一定要走哪条路呢,首先我们必须走这条路不然都没法到终点,那是不是相当于删掉这条边后图不连通了,那这不就是割边吗!!!!震惊!!!然后就是个模板题了。
总结:图论题多画图找规律,尤其这种最短路的问题。
E - Max × Sum
非常厉害的一题,我的一辈子,而且学到一种方法不亏。
按最大值排序后发现选择数集合里的最大值就是当前最右边的那个值,所以我们只要不断维护和最小即可,此时我们用优先队列来维护就好了和,当选择的数大于k就弹出最大值,等于k时就计算答案即可。
我知道,这叫反悔贪心!!!
总结:当维护最大值和求和等多种条件时可以考虑按最大值排序后单调性求解。