星期一:
dp题单 区间dp第三题 二叉搜索树 cf传送门
思路:dp【i】【j】【0/1】表示区间 i到 j,以 i / j为根节点能否形成一棵二叉搜索树
因为题目要求组成二叉搜索树,若 i 到 j 的节点为一颗完整的子树时有合法的构造方案,那么其根节点一定为 i-1 或 j+1,所以转移时先枚举区间 l r,再枚举区间内的点 k,若以 k 为根( l 到k-1为其左子树,k+1到 r为其右子树)有合法的构造方案,且 k与 l-1 或 r+1的gcd合法,则 l-1 到 r 或 l 到 r+1存在合法构造
代码如下:
ll n;
int a[770];
int dp[770][770][2];
bool gc[770][770];
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> a[i];for(int i=1;i<=n;i++){for(int j=1;j<=n;j++)gc[i][j]=(__gcd(a[i],a[j])!=1); //预处理}for(int i=1;i<=n;i++) dp[i][i][0]=dp[i][i][1]=1;for(int len=1;len<=n;len++){for(int l=1;l+len-1<=n;l++){int r=l+len-1;for(int k=l;k<=r;k++){if(dp[l][k][1] && dp[k][r][0]){if(gc[l-1][k]) dp[l-1][r][0]=1;if(gc[k][r+1]) dp[l][r+1][1]=1;}}}}for(int i=1;i<=n;i++){if(dp[1][i][1] && dp[i][n][0]){cout << "Yes"; return ;}}cout << "No";
}
dp题单 区间dp 第四题 分三角形 cf传送门
初见低级贪心题,再一细看,嗯?区间dp好题!
正解(不解释:
ll n;
void solve(){cin >> n;ll ans=0;for(int i=2;i<n;i++) ans+=i*(i+1);cout << ans;
}
而如果题目给多边形的每个点赋上权值,贪心可能就会失效,只能用下面的区间dp
思路:dp【i】【j】表示考虑 i 到 j区间最低权值
长度从3开始枚举,假设已知 i 到 k区间和 j 到 k区间的最低价值,i 到 j区间的最低价值即可能为 dp[ i ][ k ]+dp[ k ][ j ]+i*j*k,相当于 在 i,j 间连条边,形成端点为 i,j,k的三角形
代码如下:
ll n;
ll dp[550][550];
void solve(){cin >> n;for(int i=1;i<=n;i++)for(int j=i+2;j<=n;j++) dp[i][j]=1e18; //初始化记得j从i+2开始for(int len=3;len<=n;len++){for(int l=1;l+len-1<=n;l++){int r=l+len-1;for(int k=l+1;k<r;k++)dp[l][r]=min(dp[l][k]+dp[k][r]+l*r*k,dp[l][r]);}}cout << dp[1][n];
}
星期二:
dp题单 背包 第五题 军队排列 cf传送门
题意:n1个步兵,n2个骑兵,步兵不能连续站超过k1个,骑兵不能连续超过k2个,问排列方案数
思路:dp【i】【j】【k】【0/1】表示已经站了 i个步兵,j个骑兵,尾部是连续 k个步/骑的方案数,转移如下
代码如下:
const int mod=1e8;
ll n;
ll n1,n2,k1,k2;
ll dp[110][110][11][2];
void solve(){cin >> n1 >> n2 >> k1 >> k2;dp[1][0][1][0]=1,dp[0][1][1][1]=1;for(int i=0;i<=n1;i++){for(int j=0;j<=n2;j++){if(i>0){for(int k=2;k<=k1;k++)dp[i][j][k][0]=dp[i-1][j][k-1][0]; //步兵后跟步兵for(int k=1;k<=k2;k++)dp[i][j][1][0]+=dp[i-1][j][k][1],dp[i][j][1][0]%=mod; //骑兵之后放步兵}if(j>0){for(int k=2;k<=k2;k++)dp[i][j][k][1]=dp[i][j-1][k-1][1]; //骑兵后跟骑兵for(int k=1;k<=k1;k++)dp[i][j][1][1]+=dp[i][j-1][k][0],dp[i][j][1][1]%=mod; //步兵后跟骑兵}}}ll ans=0;for(int k=1;k<=k1;k++) ans+=dp[n1][n2][k][0],ans%=mod;for(int k=1;k<=k2;k++) ans+=dp[n1][n2][k][1],ans%=mod;cout << ans;
}
下午队内赛
补队内赛E cf传送门
思路:没看出来是分类讨论,6是特判-1
首先如果 x是奇数,将其分为x/2,x/2+1,一定满足互质且差最小,为1
x为偶数,若x/2为偶数,可分为x/2+1和x/2-1,差为2,而若x/2为奇数,可考虑分为x/2+2和x/2-2,但可能还有比4更好的分法
若x是3的倍数,能将其分为x/3-1,x/3,x/3+1的形式,为奇偶奇,差为2
若x%3==1,分为x/3-1,x/3,x/3+2,偶奇奇,差为3,注意特判若x/3-1为3的倍数,则不能这样分
若x%3==2,分为x/3-1,x/3+1,x/3+2,奇奇偶,差为3,同上,注意特判
代码如下:
void solve(){int tt; cin >> tt;for(int ii=1;ii<=tt;ii++){int x; cin >> x;cout << "Case #" << ii << ": ";if(x==6){cout << "-1\n"; continue;}if(x&1){cout << "1\n"; continue;}if((x/2-1)&1) cout << "2\n";else{if(x%3==0) cout << "2\n";else{if(x%3==1){if((x/3-1)%3==0) cout << "4\n";else cout << "3\n";}else{if((x/3-1)%3==0) cout << "4\n";else cout << "3\n";}}}}
}
星期三:
上午和队友vp了把23广东省赛,时间关系只打了4h,5题500罚时
记录 I 题 cf传送门
思路:涉及一点 stl 操作,还有multiset是个不错的容器,可以存放pair,再扩展下可以存结构体,在结构体里重写排序运算符,即可实现自定义的二分查找
代码如下:
ll n;
int m;
void solve(){cin >> n >> m;unordered_map<int,PII>mp;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){int x; cin >> x;mp[x]={i,j}; //存放每个点的坐标}}multiset<PII>mt;mt.insert({1,1}),mt.insert({n,m});mt.insert({0,0}); //防止查找第一行的坐标时,pit会成为野指针for(int i=0;i<=n*m-1;i++){int x=mp[i].first,y=mp[i].second;auto it=mt.lower_bound({x,y});auto pit=prev(it);int y11=pit->second;int y2=it->second;if(y>=y11 && y<=y2) mt.insert({x,y}); //判断是否在可走范围内else{cout << i << "\n"; return ;}}cout << n*m << "\n";
}
K因为os忘记多个样例清空数组,耽误了1h且惨遭3发罚时(警钟长鸣!!!
补星期二队内赛 F cf传送门
思路:4e4内的回文数很少,先全算出来,dp【x】表示组成 x的方案数,用完全背包写法
刚开始想了很久没想明白是如何进行转移的,为何可以这样转移
dp【i】【j】代表用前 i个回文数组成 j的方案数
转移方程为dp【i】【j】=dp【i-1】【j】+dp【i】【j-a [ i ]】,后俩表示用前 i-1个回文数组成 j的方案数 加上 用至少1个a【i】组成 j的方案数,再滚动一下
代码如下:
void solve(){vector<int>a;vector<ll>dp(4e4+1);for(int i=1;i<=4e4;i++){string s=to_string(i);string ss=s; reverse(ss.begin(),ss.end());if(s==ss) a.push_back(i);}dp[0]=1;for(auto i:a){for(int j=i;j<=4e4;j++) dp[j]+=dp[j-i],dp[j]%=mod;}int t; cin >> t;while(t--){int x; cin >> x;cout << dp[x] << "\n";}
}
补cf round943 div3 E cf传送门
思路:能想到在 n>=4 时曼哈顿距离能拿满,那么怎么拿呢
在主对角线放n-2个,剩下两个一个放 n-1,n,一个放 n,n,前者负责奇数曼哈顿距离,后者偶数(这也能扯到奇偶性?
代码如下:
void solve(){cin >> n;for(int i=1;i<=n-2;i++) cout << i << " " << i << "\n";cout << n-1 << " " << n << "\n" << n << " " << n << "\n";
}
补ATC abc352 D atc传送门
不知道为什么每次abc的D补着都觉得不难,但赛时就是出不了
思路:p[ i ]储存 i这个数的下标, 一个长度为 k滑动窗口不断求区间内最小值和最大值之差即可, multiset实现
代码如下:
ll n;
void solve(){int k; cin >> n >> k;vector<int>p(n+1);for(int i=1;i<=n;i++){int x; cin >> x;p[x]=i;}int ans=1e9;multiset<int>mt;for(int i=1;i<k;i++) mt.insert(p[i]);for(int i=k;i<=n;i++){mt.insert(p[i]);if(i>k) mt.erase(p[i-k]);ans=min(*prev(mt.end())-*mt.begin(),ans);}cout << ans;
}
星期四:
补23广东 B cf传送门
B - 基站建设 - SUA Wiki
思路:双指针预处理,然后用优先队列优化dp,dp【i】表示考虑到 i点且 i点建了基站的最小花费
优先队列优化不难,问题是怎么预处理出需要的信息,p【i】表示 [ p【i】,i ]间没有完整区间,因为p【i】满足单调不递减,可以用双指针求出
对于点 i来说,他能转移的点需要满足此条件,即下标需 >=p【i-1】-1(p【i-1】-1到 i-1 是一段区间所以p【i-1】-1 到 i-1 至少要建一个点
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
ll a[N];
int p[N];
vector<int>e[N];
ll dp[N];
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> a[i],e[i].clear();a[++n]=0; //使n+1且a[n+1]=0,答案就能直接取dp[n]int m; cin >> m;while(m--){int l,r; cin >> l >> r;e[l].push_back(r);e[r].push_back(-1*l);}for(int i=1,j=1,cnt=0;i<=n;i++){for(int x:e[i]) if(x<0 && -1*x>=j) cnt++; //i经过右端点且其左端点>=j,则j-i的区间数+1while(cnt>0 && j<=i){for(int x:e[j]) if(x>0 && x<=i) cnt--; //j经过左端点且其右端点<=i,则区间数-1j++;}p[i]=j;}priority_queue<PII,vector<PII>,greater<PII>>pq;dp[1]=a[1],dp[0]=0;pq.push({dp[1],1}),pq.push({dp[0],0});for(int i=2;i<=n;i++){while(pq.top().second<p[i-1]-1) pq.pop();dp[i]=pq.top().first+a[i];pq.push({dp[i],i});}cout << dp[n] << "\n";
}
补cf round943 F(异或,附特定值下标二分 cf传送门
思路:分析异或性质可得出分块只需考虑 k=2和k=3的分法
处理出异或前缀和, 若 k==2, 则存在一点 x ,分为[ l,x ]和[ x+1,r ] 即 b[x]^b[l-1]==b[r]^b[x], 只需判断 b[l-1]是否等于 b[r]即可
若k==3, 则存在两点 s,t 分为[ l,s ], [ s+1,t ], [ t+1,r ]三个区间 ,即 b[l-1]^b[s]==b[s]^b[t]==b[t]^b[r] ,即b[s]==b[r]且b[t]==b[l-1] (s<t), 则可找出下标[ l,r ]内最接近 l的值为 b[r]的数的下标,和最接近 r的值为b[l-1]的数的下标, 可以预先把一个数出现的所有下标存进 vector里实现二分查找,最后判断 (s<t)
代码如下:
const int N=2e6+10,M=210;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
ll n;
int a[N];
void solve(){int q; cin >> n >> q;map<int,vector<int>>p;p[0].push_back(0); //先塞0下标,vector里不会自动排序for(int i=1;i<=n;i++){cin >> a[i];a[i]^=a[i-1];p[a[i]].push_back(i);}while(q--){int l,r; cin >> l >> r;if(a[l-1]==a[r]){cout << "Yes\n"; continue;}int p1=*lower_bound(p[a[r]].begin(),p[a[r]].end(),l);int p2=*--lower_bound(p[a[l-1]].begin(),p[a[l-1]].end(),r);if(p1<p2) cout << "Yes\n";else cout << "No\n";}cout << "\n";
}
星期五:晚上vp 19年陕西省赛,坐了一晚上牢,晕晕
星期六:
补23广东 E 字典树 cf传送门
思路:新的字典树题,sum【i】表示以 i 为根的子树上有多少个字符串。字典树以1为根,后面的一些操作会更方便
在字典树上贪心地搜索答案,首先假设现在在根节点1上,lcp为空,那所有子节点上只能选择一个字符串,然后判断数量是否满足 >=k,若满足则结束搜索
若不满足,则需选择更多的字符串,从 a 到 z遍历子节点,判断答案下一位是否能为此字母,需满足 sum[ ch[ now ][ i ]] + 字典序<=该串的所有字符串数量 + 字典序>该串的子节点各选一个 >= k
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int k;
int ch[N][30],idx,cnt[N];
ll sum[N];
void add(string s){int p=1;sum[p]++;for(int i=0,sz=s.size();i<sz;i++){int c=s[i]-'a'+1;if(!ch[p][c]) ch[p][c]=++idx;p=ch[p][c];sum[p]++;}cnt[p]++;
}
void clr(){ //清空字典树for(int i=0;i<=idx;i++){for(int j=1;j<=26;j++) ch[i][j]=0;cnt[i]=sum[i]=0;}
}
void solve(){cin >> n >> k;idx=1;for(int i=1;i<=n;i++){string s; cin >> s;add(s);}int now=1; //表示当前节点while(1){//判断以now结尾能选的字符串数够不够int t=cnt[now];for(int i=1;i<=26;i++) if(sum[ch[now][i]]) t++; //子节点的字符串只能选一个if(t>=k){if(now==1) cout << "EMPTY";cout << "\n"; clr();return ;}//判断接下来now该去哪个节点for(int i=1;i<=26;i++) if(sum[ch[now][i]]){t+=sum[ch[now][i]],t--; //判断加上ch[now][i]里所有字符串够不够if(t>=k){ //如果够就进入该子节点cout << char('a'+i-1);now=ch[now][i];k=k-t+sum[ch[now][i]]; //k减去t但要先还回sum[ch[now][i]]break;}}}
}
补 19陕西 B cf传送门
题意:一言以蔽之,欧拉图
思路:欧拉图的做法,从入度的角度入手
若整个图不是一条循环的线,则起点入度为0,那么找一个入度为0的点模拟即可
若整个图就是一个循环,则所有点都可以是起点,从1,1开始模拟即可
然后判断是否所有点都被遍历
代码如下:
ll n;
int m;
vector<vector<int>>ve;
vector<vector<char>>ch;
vector<vector<bool>>vi;
vector<vector<bool>>in;
bool checkout(int x,int y){int dis=ve[x][y];if(ch[x][y]=='u' && x-dis<1) return 1;if(ch[x][y]=='d' && x+dis>n) return 1;if(ch[x][y]=='l' && y-dis<1) return 1;if(ch[x][y]=='r' && y+dis>m) return 1;return 0;
}
void dfs(int x,int y){vi[x][y]=1;if(checkout(x,y)) return ;int dis=ve[x][y];if(ch[x][y]=='u' && !vi[x-dis][y]) dfs(x-dis,y);if(ch[x][y]=='d' && !vi[x+dis][y]) dfs(x+dis,y);if(ch[x][y]=='l' && !vi[x][y-dis]) dfs(x,y-dis);if(ch[x][y]=='r' && !vi[x][y+dis]) dfs(x,y+dis);
}
void solve(){cin >> n >> m;ve=vector<vector<int>>(n+10,vector<int>(m+10));ch=vector<vector<char>>(n+10,vector<char>(m+10));vi=vector<vector<bool>>(n+10,vector<bool>(m+10,0));in=vector<vector<bool>>(n+10,vector<bool>(m+10,0));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++) cin >> ch[i][j];}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin >> ve[i][j];if(checkout(i,j)) continue;int dis=ve[i][j];if(ch[i][j]=='u') in[i-dis][j]=1;if(ch[i][j]=='d') in[i+dis][j]=1;if(ch[i][j]=='l') in[i][j-dis]=1;if(ch[i][j]=='r') in[i][j+dis]=1;}}bool if1=0;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)if(!in[i][j]){dfs(i,j),if1=1; break;}if(if1) break;}if(!if1) dfs(1,1);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)if(!vi[i][j]){cout << "No\n"; return ;}}cout << "Yes\n";
}
周日:
补19陕西 J 位运算贪心 cf传送门
题意:给n个区间,每个区间中选一个数,使得所有数按位与的结果最大,输出最大结果
思路:按位贪心,从高到低,对于第 i位,检查所有区间是否都存在数在此位为1,若都存在则对所有区间进行缩小操作( 也可能不变,若不存在则continue
对于如何判断一区间含有第 i位为1的数,使用位运算实现,若l【i】第 i位为 1直接返回真,否则使 l【i】第 i位为1,更低位全变为0,判断其是否小于等于 r【i】
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll l[N],r[N];
int fnd(int x,int i){if(!(x>>i&1)) x=((x>>i)|1)<<i; //第i位或上1,低位全变为0return x;
}
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> l[i] >> r[i];ll ans=0;for(int i=30;i>=0;i--){bool if1=1;for(int j=1;j<=n;j++){if(l[j]>>i&1) continue;int nl=fnd(l[j],i);if(nl>r[j]){if1=0; break;}}if(!if1) continue;ans+=1ll<<i;for(int j=1;j<=n;j++){if(l[j]>>i&1) continue;l[j]=fnd(l[j],i);}}cout << ans << "\n";
}
补ABC 353 D atc传送门
日常卡D然后补题又觉得不难
思路:做一个位数的前缀和,就能计算出一个数作为前缀的总贡献
代码如下:
const int N=2e6+10,M=210;
const int mod=998244353;
ll n;
int a[N];
int sum[N][11];
ll qpow(int a,int n){if(n==0) return 1;if(n==1) return a;ll s=qpow(a,n/2);s=s*s%mod;if(n&1) s=s*a%mod;return s;
}
void solve(){cin >> n;for(int i=1;i<=n;i++){cin >> a[i];string s=to_string(a[i]);for(int j=1;j<11;j++) sum[i][j]=sum[i-1][j];sum[i][s.size()]++;}ll ans=0;for(int i=1;i<=n;i++){for(int j=1;j<11;j++)ans+=(1ll*a[i]*qpow(10,j)%mod)*(sum[n][j]-sum[i][j])%mod;//前缀贡献ans+=1ll*a[i]*(i-1),ans%=mod; //后缀贡献可直接算出}cout << ans;
}