3.26 - 4.14

Posted 1625--h

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
",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
",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
",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
",ans);
        }
    }
    return 0;
}

CF-1140 E - Palindrome-less Arrays

题意:给定一个没有填完的序列,数值为-1表示你可以用 1~k 中的数字去覆盖它,求将该序列填充后,不存在长度为奇数的回文串的方案数

分析:

  1. 使之不存在长度为奇数的回文串,只需要满足不存在长度为3的回文串即可。换句话说:(a[i] eq a[i+2]) 对所有的 (i) 成立。可以发现 i 为奇数与 i 为偶数是互不影响的。所以可以把它划分为两个串

    1. 一个串由 $a_1,a_3,a_5, dots $组成
    2. 另一个串由$ a_2,a_4,a_6,dots$ 组成
  2. 现在问题转化为了:给定一个序列,将其数值为-1的位置换为1~k中的数字,使得序列中两两相邻数字不同的方案数。不妨换个角度想,任何一组连续的 -1(长度可以为0或1),两边都只有四种情况

    1. 两边都没有数字(即整个串都是-1)
    2. 两边中有一边没有没有(只有整个串的左右两端有这种情况)
    3. 两边的数字相同
    4. 两边的数字不同

    另外我们可以发现,前两种情况可以由后两种情况推出来,所以只需预处理把 0~ (n/2)+1长度的-1串的方案数都预处理出来,问题就迎刃而解了。

  3. (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 为奇数的情况,我们可以取出这个序列的中间位置 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
",(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
",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
",res[i]);
    return 0;
}
结语:

最近做了一些水题吧,感觉并没有什么提高,一遇到有思维量的题目就over了。昨天打比赛打的自闭了,也正因为这样,才把拖欠了两周的记录补上,其实平时也在写着,只不过很凌乱,最近这两周正在看第二遍蓝书。暑假前要把规定的内容过完啊。加油

以上是关于3.26 - 4.14的主要内容,如果未能解决你的问题,请参考以下文章

3.26全部完成android和web端的地铁查询系统

代码:程序清单4.14_longstrg.c程序_《C Primer Plus》P77

每日随笔3.26

3.26上机

3.26抽象方法,接口

3.26学习周报