3.26 - 4.14
3.26-4.14
CF-629D
- 求上升子序列的最大和。O(n^2)会暴力,在查询的时候要用线段树维护
- 因为权值是浮点数,故先离散化一下,设第 i 个位置的权值,从小到大排名为 id。那么dp转移中 $$d[i] = max(d[i],d[i] + d[j])$$ 其中$$j<i & id[j]<id[i]$$ ,故线段树结点区间
[l,r]
维护的是id = l 到 id = j 中的最大 dp值
#include <bits/stdc++.h>
using namespace std;
const int N = 100000;
double vol[N],r,h;
int n,has[N],g[N],dp[N],tot;
int c[N];
vector<double> v;
int getId(double a){
return lower_bound(v.begin(),v.end(),a)-v.begin()+1;
}
struct Tree{
int l,r;
double data;
}t[4*N];
void build(int p,int l,int r){
t[p].l = l;
t[p].r = r;
if(l==r){
t[p].data = 0;return;
}
int mid = l+r>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].data = 0;
}
void change(int p,int x,double val){
if(t[p].l == t[p].r && t[p].l == x){
t[p].data = max(t[p].data,val);
return ;
}
int mid = (t[p].l+t[p].r)>>1;
if(x<=mid)change(p*2,x,val);
else change(p*2+1,x,val);
t[p].data = max(t[p*2].data,t[p*2+1].data);
}
double ask(int p,int l,int r){
if(t[p].l>=l&&t[p].r<=r)return t[p].data;
int mid = (t[p].l+t[p].r)>>1;
double val = 0;
if(l<=mid)val = max(val,ask(p*2,l,r));
if(r>mid)val = max(val,ask(p*2+1,l,r));
return val;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lf%lf",&r,&h);
vol[i] = acos(-1) * r * r * h;
v.push_back(vol[i]);
}
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
build(1,1,n);
double res = 0;
for(int i=1;i<=n;i++){
int id = getId(vol[i]);
double now = ask(1,1,id-1);
now = max(vol[i],vol[i]+now);
res = max(res,now);
change(1,id,now);
}
printf("%.10lf\n",res);
return 0;
}
- 辗转了很多次,惭愧,线段树做的题太少了
法二:离散化+树状数组
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
const int inf = 0x3f3f3f3f;
double vol[N],c[N];
vector<double> v;
int n,g[N];
int getId(double res){
return lower_bound(v.begin(),v.end(),res) - v.begin() + 1;
}
void add(int x,double y){
c[x] = y;
for(;x<=n;x+=x&-x)c[x] = max(c[x],y);
}
double ask(int x){
double res = 0;
for(;x;x-=x&-x)res = max(res,c[x]);
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
double r,h;
scanf("%lf%lf",&r,&h);
double vo = acos(-1) * r * r * h;
vol[i] = vo;
v.push_back(vo);
}
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
double res = 0;
for(int i=1;i<=n;i++){
int x = getId(vol[i]);
double now = ask(x-1) + vol[i];
add(x,now);
res = max(res,now);
}
printf("%.10lf\n",res);
return 0;
}
CF-332B
题意:给定长度为n的序列,寻找两段不相交的,长度为k的序列,使其权值和最大
怎么都能做,其实类似单调队列的思想,只需要一个变量保存最优决策即可
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
typedef long long ll;
int n,k;
ll a[N],sum[N];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum[i] = sum[i-1]+a[i];
}
int pre = 1,l = 1,r = k+1,fi = 1,se = 1;
ll res = 0;
for(;r<=n-k+1;r++){
if(sum[pre+k-1]-sum[pre-1] < sum[r-1]-sum[r-1-k])pre = r-k;
if(sum[r+k-1]-sum[r-1]+sum[pre+k-1]-sum[pre-1] > res){
res = sum[r+k-1]-sum[r-1]+sum[pre+k-1]-sum[pre-1];
fi = pre;
se = r;
}
}
printf("%d %d\n",fi,se);
return 0;
}
CF-1043D
-
题意:有间隔为k的n个点在数轴上,下标为 \(1,k+1, 2*k+1, (n-1)*k+1\) 首尾相接。设起点为s,步长为L,而现在只知道s距离最近的点的距离为a,和(s+L)距离最近的点的距离为b。问从s出发,第一次回到s走的最多和最少的步数。
-
分析:设走x步回到起点,那么有\(x*l = t * n * k\) 即走了x步饶了 t 圈
又因为x和t互质,即保证是第一次回到s,所以有 \(x = {n * k \over gcd(n*k, l)}\) 。所以枚举所有可能的 l ,得到gcd的最大值和最小值即可。
-
l (l<k时)的取值只有四种情况,画图即可得知
- \(l = k-a-b\)
- \(l = k+b-a\)
- \(l = a+b\)
- \(l = k+a-b\)
然后每一种情况又可以在原来的基础上多加 i 个k。总共4*n种 l
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k,a,b;
int main(){
cin>>n>>k>>a>>b;
ll mi = LONG_LONG_MAX;
ll mx = 0;
for(int i=0;i<n;i++){
ll x1 = __gcd(n*k,k-a-b + i*k);
ll x2 = __gcd(n*k,k+b-a + i*k);
ll x3 = __gcd(n*k,a+b + i*k);
ll x4 = __gcd(n*k,k+a-b + i*k);
mi = min(mi,min(x1,min(x2,min(x3,x4))));
mx = max(mx,max(x1,max(x2,max(x3,x4))));
}
cout<<(n*k)/mx<<' '<<(n*k)/mi<<endl;
return 0;
}
CF-620 E - New Year Tree (状态压缩+线段树)
#include <bits/stdc++.h>
using namespace std;
const int N = 400010;
typedef long long ll;
int n,m,cnt,c[N],num[N],id[N];
ll color[N];
vector<int> v[N];
void dfs(int x,int fa){
num[x] = 1;
color[++cnt] = 1ll<<c[x];
id[x] = cnt;
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(y==fa)continue;
dfs(y,x);
num[x] += num[y];
}
}
struct SegTree{
int l,r;
ll tag;
ll data;
}t[4*N];
void build(int p,int l,int r){
t[p].l = l;t[p].r = r;
if(l==r){
t[p].data = color[l];
return ;
}
int mid = l+r>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].data = t[p*2].data | t[p*2+1].data;
}
void spread(int p){
if(t[p].tag){
t[p*2].data = t[p].data;
t[p*2+1].data = t[p].data;
t[p*2].tag = 1;
t[p*2+1].tag = 1;
t[p].tag = 0;
}
}
void change(int p,int l,int r,int val){
if(t[p].l>=l&&t[p].r<=r){
t[p].data = 1ll<<val;
t[p].tag = 1;
return;
}
spread(p);
int mid = t[p].l+t[p].r>>1;
if(l<=mid)change(p*2,l,r,val);
if(r>mid)change(p*2+1,l,r,val);
t[p].data = t[p*2].data | t[p*2+1].data;
}
ll ask(int p,int l,int r){
if(t[p].l>=l&&t[p].r<=r){
return t[p].data;
}
spread(p);
int mid = t[p].l+t[p].r>>1;
ll res= 0;
if(mid>=l)res |= ask(p*2,l,r);
if(mid<r)res |= ask(p*2+1,l,r);
return res;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%d",&c[i]);
for(int i=1;i<=n-1;i++){
int x,y;scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,-1);
build(1,1,n);
for(int i=1;i<=m;i++){
int k,x,y;
scanf("%d%d",&k,&x);
if(k==1){
scanf("%d",&y);
change(1,id[x],id[x]+num[x]-1,y);
}
else{
ll res = ask(1,id[x],id[x]+num[x]-1);
int ans = 0;
while(res){if(res&1)ans++;res/=2;}
printf("%d\n",ans);
}
}
return 0;
}
CF-1140 E - Palindrome-less Arrays
题意:给定一个没有填完的序列,数值为-1表示你可以用 1~k 中的数字去覆盖它,求将该序列填充后,不存在长度为奇数的回文串的方案数
分析:
-
使之不存在长度为奇数的回文串,只需要满足不存在长度为3的回文串即可。换句话说:\(a[i] \neq a[i+2]\) 对所有的 \(i\) 成立。可以发现 i 为奇数与 i 为偶数是互不影响的。所以可以把它划分为两个串
- 一个串由 $a_1,a_3,a_5, \dots $组成
- 另一个串由$ a_2,a_4,a_6,\dots$ 组成
-
现在问题转化为了:给定一个序列,将其数值为-1的位置换为1~k中的数字,使得序列中两两相邻数字不同的方案数。不妨换个角度想,任何一组连续的 -1(长度可以为0或1),两边都只有四种情况
- 两边都没有数字(即整个串都是-1)
- 两边中有一边没有没有(只有整个串的左右两端有这种情况)
- 两边的数字相同
- 两边的数字不同
另外我们可以发现,前两种情况可以由后两种情况推出来,所以只需预处理把 0~ (n/2)+1长度的-1串的方案数都预处理出来,问题就迎刃而解了。
-
设\(d(i,j)\) 表示长度为 \(i\) 的 -1 串,j 为0 表示两边数字相同,为1表示两边数字不同时的方案数,\(d[0][0] = 0, d[0][1] = 1\), 有转移方程:
- \(i\) 为奇数
- $d[i][0] = d[i/2][0]d[i/2][0] + (k-1)d[i/2][1]*d[i/2][1] $
- \(d[i][1] = d[i/2][0]*d[i/2][1]*2 + (k-2)*d[i/2][1]*d[i/2][1]\)
- \(i\) 为偶数
- \(d[i][0] = (k-1)*d[i-1][1]\)
- \(d[i][1] = d[i-1][0] + (k-2)*d[i-1][1]%mod\)
- \(i\) 为奇数
对于 i 为奇数的情况,我们可以取出这个序列的中间位置 mid,当 -1 串两端数字相同且都等于 x 时,先假设mid数字与x相同,那就转换为了两个长度为 i/2,序列两端相同 的子问题,然后假设 mid 与 x不同,那么就有(k-1)种方法,可以同样转换成两个长度为 i/2 ,序列两端不同的子问题。当 -1 串两端数字不同时,同理。
预处理d数组之后,就可以对我们之前分好的奇偶串做处理了。思路就是记录上一个不为-1的位置。然后最后做一下特判,就可以得到正确答案了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
ll d[100010][2];
int a[100010],b[100010];
ll n,k;
ll solve(int *a,ll len){
ll res = 1;
ll last = 0;
for(ll i=1;i<=len;i++){
if(a[i] == -1)continue;
else{
if(i == 1){
last = i;continue;
}
if(last == 0){
res = res * (d[i-2][0] + (k-1)*d[i-2][1])%mod;
}
else{
if(a[i] == a[last]){
res = res * d[i-last-1][0]%mod;
}
else res = res * d[i-last-1][1]%mod;
}
last = i;
}
}
if(last==0){
res = k;
for(int i=2;i<=len;i++)res = (res*(k-1))%mod;
}
else if(last !=len){
res = res * (d[len-last-1][0] + (k-1)*d[len-last-1][1]%mod)%mod;
}
return res;
}
int main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
if(i&1)scanf("%d",&a[(i+1)/2]);
else scanf("%d",&b[i/2]);
}
d[0][0] = 0;d[0][1] = 1;
for(int i=1;i<=(n+1)/2;i++){
if(i&1){
int len = i/2;
d[i][0] = (d[len][0] * d[len][0]%mod + (k-1) * d[len][1]%mod * d[len][1]%mod)%mod;
d[i][1] = (d[len][0] * d[len][1]%mod * 2%mod + (k-2) * d[len][1]%mod * d[len][1]%mod)%mod;
}
else{
d[i][0] = (d[i-1][1] * (k-1)) % mod;
d[i][1] = (d[i-1][0] + (k-2) * d[i-1][1]%mod)%mod;
}
}
printf("%lld\n",(solve(a,(n+1)/2)*solve(b,n-(n+1)/2))%mod);
return 0;
}
CH6101 最优贸易(最短路)
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
const int M = 500010;
int val[N],n,m,d[N],f[N],vis[N];
vector<int> v[N];
struct edge{
int x,y,k;
}e[M];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].k);
v[e[i].x].push_back(e[i].y);
if(e[i].k == 2)v[e[i].y].push_back(e[i].x);
}
memset(d,0x3f,sizeof d);
d[1] = val[1];
priority_queue< pair<int,int> > q;
q.push(make_pair(-val[1],1));
while(!q.empty()){
int x = q.top().second;q.pop();
if(vis[x])continue;
vis[x] = 1;
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(d[y] > min(val[y],d[x])){
d[y] = min(val[y],d[x]);
q.push(make_pair(-d[y],y));
}
}
}
for(int i=1;i<=n;i++){
v[i].clear();vis[i] = 0;
}
for(int i=1;i<=m;i++){
v[e[i].y].push_back(e[i].x);
if(e[i].k == 2)v[e[i].x].push_back(e[i].y);
}
q.push(make_pair(val[n],n));
f[n] = val[n];
while(!q.empty()){
int x = q.top().second;q.pop();
if(vis[x])continue;
vis[x] = 1;
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(f[y] < max(val[y],f[x])){
f[y] = max(val[y],f[x]);
q.push(make_pair(f[y],y));
}
}
}
int res = 0;
for(int i=1;i<=n;i++){
res = max(res,f[i]-d[i]);
}
cout<<res<<endl;
return 0;
}
CF-1141F2 - Same Sum Blocks (Hard) <贪心,奇技淫巧>
题意:长度为n(n<=1500)的序列,使得分成尽量多的区间,使得每个区间加起来的和都相等。
分析:用map存起来每个和对应的区间的左右端点map<int, vector<pair<int,int>>> segs;
,然后对于同一和,试着确定可以分出最多多少个互不相交的区间。一个比较容易想到的贪心策略是,右端点递增的依次选择区间,那么我们一开始筛选区间的时候,就按照右端点从左到右的顺序就可以了
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
map<int, vector<pair<int,int>>> segs;
//按照右端点从左到右的存取区间
for (int r = 0; r < n; r++) {
int sum = 0;
for (int l = r; l >= 0; l--) {
sum += a[l];
segs[sum].push_back({l, r});
}
}
int result = 0;
vector<pair<int,int>> best;
for (const auto& p: segs) {
//const 引用,加快速度
const vector<pair<int,int>>& pp = p.second;
int cur = 0;
int r = -1;
//存取答案
vector<pair<int,int>> now;
for (auto seg: pp)
if (seg.first > r) {
cur++;
now.push_back(seg);
r = seg.second;
}
if (cur > result) {
result = cur;
best = now;
}
}
cout << result << endl;
for (auto seg: best)
cout << seg.first + 1 << " " << seg.second + 1 << endl;
return 0;
}
1119 D - Frets On Fire
思维题,不难啊,为啥就被唬住了。这种难度估计练铜牌题都不算。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef unsigned long long ll;
ll s[N],n,q,c[N],sum[N];
int main(){
scanf("%lld",&n);
for(int i=0;i<n;i++){
scanf("%lld",&s[i]);
}
sort(s,s+n);
for(int i=1;i<n;i++)
c[i] = s[i] - s[i-1];
sort(c+1,c+n);
for(int i=1;i<n;i++)sum[i] = sum[i-1] + c[i];
scanf("%lld",&q);
while(q--){
ll l,r;
scanf("%lld%lld",&l,&r);
ll len = r-l+1;
ll res = s[n-1] - s[0];
ll pos = upper_bound(c+1,c+n,len) - c;
res -= (sum[n-1] - sum[pos-1]) - (n-pos) * len;
res += r-l + 1;
printf("%lld\n",res);
}
return 0;
}
1119 E - Pavel and Triangles
贪心啊,过不了肯定就是贪错了,优先1+2的匹配方案
#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
typedef long long ll;
ll a[N],n;
int main(){
scanf("%d",&n);
long long res = 0;
for (int i = 0; i < n; ++i)
{
scanf("%lld",&a[i]);
}
queue<ll> q;
ll last = 0;
ll now = 0;
for(int i=0;i<n;i++){
ll add = min(a[i]/2,last);
res += add;
last -= add;
a[i] -= add * 2;
res += a[i]/3;
last += a[i]%3;
}
cout<<res<<endl;
return 0;
}
1139 E - Maximize Mex
从最后建图,进行二分图匹配。
#include <bits/stdc++.h>
using namespace std;
const int N = 5050;
int n,m,p[N],c[N],d,del[N],res[N],k[N];
vector<int> v[N];
int match[N],vis[N];
bool dfs(int x){
for(int i=0;i<v[x].size();i++){
int y = v[x][i];
if(vis[y])continue;
vis[y] = 1;
//如果y没有匹配过,或者继续dfs下去可以成功,则当前match[y]=x,而消除vis[y]只是为了下一圈寻找时不必要初始化vis[y]
if(match[y]==-1 || dfs(match[y])){
match[y] = x;
vis[y] = 0;
return true;
}
vis[y] = 0;
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=1;i<=n;i++)scanf("%d",&c[i]);
scanf("%d",&d);
for(int i=1;i<=d;i++){
scanf("%d",&k[i]);
del[k[i]]=1;
}
memset(match,-1,sizeof match);
for(int i=1;i<=n;i++)if(del[i] == 0)v[p[i]].push_back(c[i]);
int j=0;//Mex肯定是从0增加的
for(int i=d;i>=1;i--){
int id = k[i];
for(;j<=5000;j++){
if(!dfs(j))break;//如果没有匹配到,则当前的Mex为j
}
res[i] = j;
v[p[id]].push_back(c[id]);//然后把删除的这个加进去
}
for(int i=1;i<=d;i++)
printf("%d\n",res[i]);
return 0;
}
结语:
最近做了一些水题吧,感觉并没有什么提高,一遇到有思维量的题目就over了。昨天打比赛打的自闭了,也正因为这样,才把拖欠了两周的记录补上,其实平时也在写着,只不过很凌乱,最近这两周正在看第二遍蓝书。暑假前要把规定的内容过完啊。加油