2023 集训记录

2023.10.9 NOIP2023-div2模拟赛15

比赛链接

A. 华灵

显然,一个合法括号对序列的长度必须为奇数,所以当 \(n\)\(m\) 为奇数的时候,直接构造。下考虑 \(n\)\(m\) 都是偶数的情况。设 \(n\leq m\) 的情况。

其他情况,可以构造出两种方案,\(n/2+m-1\)\(n+m-4\),取 \(\texttt{min}\) 即可。具体构造见代码。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int main(){
    freopen("butterfly.in","r",stdin);
    freopen("butterfly.out","w",stdout);
    scanf("%d%d",&n,&m);
    if(n&1){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(j&1)printf("(");
                else printf(")");
            }
            printf("\n");
        }
    }
    else if(m&1){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(i&1)printf("(");
                else printf(")");
            }
            printf("\n");
        }
    }
    else if(n>m){
        if(n+m/2-1>n+m-4){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    if(i&1){
                        if(j&1)printf("(");
                        else printf(")");
                    }
                    else{
                        if(j==1)printf("(");
                        else if(j==m)printf(")");
                        else if(j&1)printf(")");
                        else printf("(");
                    }
                }
                printf("\n");
            }
        }
        else{
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    if(i==1||j==1)printf("(");
                    else if(i==n||j==m)printf(")");
                    else if((i+j)&1)printf("(");
                    else printf(")");
                }
                printf("\n");
            }
        }
    }
    else{
        if(m+n/2-1>n+m-4){
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    if(j&1){
                        if(i&1)printf("(");
                        else printf(")");
                    }
                    else{
                        if(i==1)printf("(");
                        else if(i==n)printf(")");
                        else if(i&1)printf(")");
                        else printf("(");
                    }
                }
                printf("\n");
            }
        }
        else{
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    if(i==1||j==1)printf("(");
                    else if(i==n||j==m)printf(")");
                    else if((i+j)&1)printf("(");
                    else printf(")");
                }
                printf("\n");
            }
        }
    }
    return 0;
}

B. 最近公共祖先

方法一(正解)

考虑加入一个黑点更新其他点。暴力的做法是不停地往上跳,用线段树维护一个 \(\texttt{RMQ}\),更新除这个点跳上去的子树以外的其他点。

考虑到一件事情:如果一个子树里有两个黑点,那么整棵子树都能被这个节点更新,那么后面更新到这个点都不会再有贡献,所以直接 break 掉即可。

这样一个点最多被算 \(2\) 次,总复杂度 \(\mathcal{O}(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=2e5+10,INF=0x3f3f3f3f;
int n,m,a[N],fa[N];
int head[N],nxt[M],to[M],cnt=1;
int dfn[N],tim=0,ed[N];
bool used[N];
void addEdge(int u,int v){
    nxt[++cnt]=head[u];
    head[u]=cnt;
    to[cnt]=v;
}
void dfs(int u,int p){
    fa[u]=p;
    dfn[u]=++tim;
    for(int i=head[u];i;i=nxt[i]){
        int v=to[i];
        if(v==p)continue;
        dfs(v,u);
    }
    ed[u]=tim;
}
struct Node{
    int l,r;
    int val,tag;
}tree[N<<2];
void pushup(int x){
    tree[x].val=max(tree[x<<1].val,tree[x<<1|1].val);
}
void pushdown(int x){
    if(tree[x].tag==-INF)return;
    tree[x<<1].val=max(tree[x<<1].val,tree[x].tag);
    tree[x<<1|1].val=max(tree[x<<1|1].val,tree[x].tag);
    tree[x<<1].tag=max(tree[x<<1].tag,tree[x].tag);
    tree[x<<1|1].tag=max(tree[x<<1|1].tag,tree[x].tag);
    tree[x].tag=-INF;
}
void build(int x,int l,int r){
    tree[x].l=l,tree[x].r=r;
    tree[x].val=tree[x].tag=-INF;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
}
void update(int x,int l,int r,int v){
    if(tree[x].l>=l&&tree[x].r<=r){
        tree[x].val=max(tree[x].val,v);
        tree[x].tag=max(tree[x].tag,v);
        return;
    }
    pushdown(x);
    int mid=(tree[x].l+tree[x].r)>>1;
    if(l<=mid)update(x<<1,l,r,v);
    if(r>mid)update(x<<1|1,l,r,v);
    pushup(x);
}
int query(int x,int p){
    if(tree[x].l==tree[x].r)return tree[x].val;
    pushdown(x);
    int mid=(tree[x].l+tree[x].r)>>1;
    if(p<=mid)return query(x<<1,p);
    else return query(x<<1|1,p);
}
int main(){
    freopen("lca.in","r",stdin);
    freopen("lca.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
    }
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        addEdge(u,v);
        addEdge(v,u);
    }
    dfs(1,0);
    build(1,1,n);
    char op[10];
    int u;
    while(m--){
        scanf("%s%d",op,&u);
        if(op[0]=='M'){
            int v=u,lst=0;
            while(v&&!used[v]){
                used[v]=1;
                if(v==u)update(1,dfn[v],ed[v],a[v]);
                else{
                    if(dfn[v]<dfn[lst])update(1,dfn[v],dfn[lst]-1,a[v]);
                    if(ed[lst]<ed[v])update(1,ed[lst]+1,ed[v],a[v]);
                }
                lst=v;
                v=fa[v];
            }
            if(v)update(1,dfn[v],ed[v],a[v]);
        }
        else{
            int res=query(1,dfn[u]);
            if(res==-INF)printf("-1\n");
            else printf("%d\n",res);
        }
    }
    return 0;
}
方法二(询问分块,会 TLE 80 pts,powered by Sai_tqwq)

Sai_tqwq 提供的一个时间复杂度为 \(\mathcal{O}(n\sqrt{n\log n})\) 的做法。

询问分块,每 \(\sqrt{n}\) 次重构一次,散点暴力,整块的可以 \(\texttt{DFS}\) \(\mathcal{O}(n)\) 处理。

题解有一个这个做法,但是被 Sai_tqwq 卡掉了,讨论

C. 樱符

妙妙题。主要是观察一个性质。

性质:因为 \(\forall u,v,\texttt{maxflow}(u,v)\leq 2\),则原图是一个仙人掌。

证法是利用最大流等于最小割,然后显然如果不是仙人掌,至少割三条边。

知道仙人掌以后,就会发现任意两点的最大流,即最小割,要么割掉一个桥边,要么割掉环上的两条边。

发现如果要割环上的两条边,必然要割最小的边,那就删掉最小的边,并且把剩下的环上的边权都加上最小的边权。

因为是仙人掌,所以剩下形成一个数,两个点的最大流为树上路径的和。把边从大到小加入,用并查集维护 \(p^{(i-1)n}\)\(p^i\) 即可。

时间复杂度 \(\mathcal{O}(m\log n)\)

D. 收容

不甚牛。感觉不是很难想(?

首先,将输入质因数分解。然后从大到小枚举 \(b_i\),用一个 set 维护上下两者之商的最小值即可(或者用 \(\texttt{segment tree}\) 也可以)。

时间复杂度 \(\mathcal{O}(V\log V)\)

2023.10.10 NOIP2023-div2模拟赛16

比赛链接

A. 正方形

搞笑了,Sai_tqwq 造了个极限数据卡掉了几乎所有 \(O(nd)\) 的代码,然后数据把 \(O(nd^2)\) 都放过去了?卡了一整场常数结果第一发就过了??

讲一下 \(O(nd)\) 的做法罢。显然,每个单位正方形都只会被分成 \(4\) 份,维护 \(0\sim 15\) 的状态即可表示每个正方形的状态。

对每一列进行一个差分即可,斜着的正方形需要维护边上斜着的三角形。时间复杂度 \(O(nd)\)

B. 完全背包问题

没有限制就是同余最短路板子题。考虑到 \(L\)\(C\) 都很小,这样直接背包即可。

2023.10.12 NOIP2023-div2模拟赛17

比赛链接

A. yxh 与数组

简单题。直接暴力处理 \(x!\) 的质因数分解即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
// bool st;
const int N=1e7+10;
bool ispri[N];
int pri[N],cnt=0;
int cur[N];
int q;
// bool en;
void precalc()
{
    for(int i=2;i<N;i++)
    {
        if(!ispri[i])
        {
            pri[++cnt]=i;
        }
        for(int j=1;j<=cnt&&i*pri[j]<N;j++)
        {
            ispri[i*pri[j]]=1;
            if(i%pri[j]==0)
            {
                break;
            }
        }
    }
}
void calc(int x,int v)
{
    for(int i=1;i<=cnt&&pri[i]<=x;i++)
    {
        int num=0,tmp=x;
        while(tmp)
        {
            tmp/=pri[i];
            num+=tmp;
        }
        cur[i]+=(v==1?num:-num);
    }
}
void solve()
{
    int a,b,c,d;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    for(int i=1;i<=cnt;i++)
    {
        cur[i]=0;
    }
    calc(a-1,1);
    calc(b,-1);
    calc(c-1,-1);
    calc(d,1);
    for(int i=1;i<=cnt;i++)
    {
        if(cur[i]<0)
        {
            printf("NE\n");
            return;
        }
    }
    printf("DA\n");
}
int main()
{
    freopen("array.in","r",stdin);
    freopen("array.out","w",stdout);
    precalc();
    scanf("%d",&q);
    while(q--)
    {
        solve();
    }
    return 0;
}

B. 谦逊

发现次数不会太多,直接暴搜即可。(P.S. 赛时写的贪心,假掉了。)

点击查看代码
#include <bits/stdc++.h>

#define int long long

using namespace std;

typedef pair<int, int> pii;

int mn = 10, res;

inline int get(int x) {
    int sum = 0;
    while(x) {
        sum += x % 10;
        x /= 10;
    }
    return sum;
}

inline pii calc(int x) {
    int cnt = 0;
    while(x >= 10) {
        cnt++;
        x = get(x);
    }
    return make_pair(x, cnt);
}

int n, k;

void dfs(int x, int cur) {
    if(x > 14) return;
    if(cur < mn) {
        mn = cur;
        res = x;
    } else if(cur == mn && x < res) res = x;
    dfs(x + 1, cur + k);
    dfs(x + 1, get(cur));
}

void solve() {
    cin >> n >> k;
    mn = 10;
    dfs(0, n);
    cout << mn << " " << res << "\n";
}

signed main() {
    freopen("humility.in", "r", stdin);
    freopen("humility.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while(T--) solve();
    return 0;
}

C. 基础fake 练习题

贪心地选深度最大的链即可,树链剖分更新,赛时被数据卡常了(难过。

点击查看代码
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
inline int min(int x,int y)
{
    return x<y?x:y;
}
const int N=3e5+10,M=6e5+10;
int n,m,c[N];
int head[N],nxt[M],to[M],cnt=1;
pii q[N];
int dep[N],siz[N],son[N],top[N],fa[N];
int dfn[N],tim=0,rnk[N];
int val[N<<2],tag[N<<2];
void addEdge(int u,int v)
{
    nxt[++cnt]=head[u];
    head[u]=cnt;
    to[cnt]=v;
}
void dfs(int u,int p)
{
    fa[u]=p;
    dep[u]=dep[p]+1;
    siz[u]=1;
    son[u]=-1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==p)
        {
            continue;
        }
        dfs(v,u);
        siz[u]+=siz[v];
        if(son[u]==-1||siz[v]>siz[son[u]])
        {
            son[u]=v;
        }
    }
}
void redfs(int u,int t)
{
    dfn[u]=++tim;
    rnk[tim]=u;
    top[u]=t;
    if(~son[u])
    {
        redfs(son[u],t);
    }
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa[u]||v==son[u])
        {
            continue;
        }
        redfs(v,v);
    }
}
bool cmp(pii x,pii y)
{
    return dep[x.first]>dep[y.first];
}
void pushup(int x)
{
    val[x]=min(val[x<<1],val[x<<1|1]);
}
void pushdown(int x)
{
    if(!tag[x])
    {
        return;
    }
    val[x<<1]-=tag[x];
    val[x<<1|1]-=tag[x];
    tag[x<<1]+=tag[x];
    tag[x<<1|1]+=tag[x];
    tag[x]=0;
}
void build(int x,int l,int r)
{
    tag[x]=0;
    if(l==r)
    {
        val[x]=c[rnk[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x);
}
void update(int x,int l,int r,int ql,int qr)
{
    if(l>=ql&&r<=qr)
    {
        val[x]--;
        tag[x]++;
        return;
    }
    pushdown(x);
    int mid=(l+r)>>1;
    if(ql<=mid)
    {
        update(x<<1,l,mid,ql,qr);
    }
    if(qr>mid)
    {
        update(x<<1|1,mid+1,r,ql,qr);
    }
    pushup(x);
}
int query(int x,int l,int r,int ql,int qr)
{
    if(l>=ql&&r<=qr)
    {
        return val[x];
    }
    pushdown(x);
    int mid=(l+r)>>1,res=1e9;
    if(ql<=mid)
    {
        res=query(x<<1,l,mid,ql,qr);
    }
    if(qr>mid)
    {
        res=min(res,query(x<<1|1,mid+1,r,ql,qr));
    }
    return res;
}
bool check_chain(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(!query(1,1,n,dfn[top[v]],dfn[v]))
        {
            return 0;
        }
        v=fa[top[v]];
    }
    if(!query(1,1,n,dfn[u],dfn[v]))
    {
        return 0;
    }
    return 1;
}
void update_chain(int u,int v)
{
    while(top[u]!=top[v])
    {
        update(1,1,n,dfn[top[v]],dfn[v]);
        v=fa[top[v]];
    }
    update(1,1,n,dfn[u],dfn[v]);
}
int main()
{
    freopen("fake.in","r",stdin);
    freopen("fake.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>c[i];
    }
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        addEdge(u,v);
        addEdge(v,u);
    }
    dfs(1,0);
    redfs(1,1);
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        cin>>q[i].first>>q[i].second;
        if(dep[q[i].first]>dep[q[i].second])
        {
            swap(q[i].first,q[i].second);
        }
    }
    sort(q+1,q+m+1,cmp);
    int ans=0;
    for(int i=1;i<=m;i++)
    {
        if(check_chain(q[i].first,q[i].second))
        {
            ans++;
            update_chain(q[i].first,q[i].second);
        }
    }
    cout<<ans<<"\n";
    return 0;
}

D. 基础图论练习题

too hard, gugugu.

2023.10.13 NOIP2023-div2模拟赛18

比赛链接

A. 橙子

通过打表 / 感性理解 / 推柿子可以发现,偶数长度一定为 \(0\),奇数 \(l,l+2,\cdots,r\)\(1\),用树状数组维护下标奇偶性前缀和即可。

点击查看代码
#include <bits/stdc++.h>
// #define int long long
#define F first
#define S second
#define pb push_back
#define mpr make_pair
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
const int N=2e5+10;
int n,q,a[N];
struct FenwickTree
{
    int tree[N];
    void update(int x,int v)
    {
        while(x<=n)
        {
            tree[x]^=v;
            x+=x&-x;
        }
    }
    int query(int x)
    {
        int res=0;
        while(x>0)
        {
            res^=tree[x];
            x-=x&-x;
        }
        return res;
    }
}T[2];
signed main()
{
    freopen("orange.in","r",stdin);
    freopen("orange.out","w",stdout);
    scanf("%d%d",&n,&q);
    rep(i,1,n)
    {
        scanf("%d",a+i);
        T[i&1].update(i,a[i]);
    }
    while(q--)
    {
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1)
        {
            T[x&1].update(x,a[x]);
            T[x&1].update(x,y);
            a[x]=y;
        }
        else
        {
            if((y-x+1)%2==0)
            {
                printf("0\n");
            }
            else
            {
                int ans=(T[x&1].query(y)^T[x&1].query(x-1));
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

B. 保护

原题可以转化为:将 \(a_i\) 从大到小排序,每个 \(a_i\) 对应一个 \((0,0)-(i,a_i)\) 的矩形,求选出 \(m\) 个矩形的最小面积并。

最暴力的 DP 是 \(O(n^2m)\) 的,用斜率优化一下就可以变成 \(O(nm)\) 了。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define F first
#define S second
#define pb push_back
#define mpr make_pair
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=2e3+10;
const int INF=1e12+10;
int n,m,a[N];
int dp[N][N];
int stk[N],top=0;
void solve()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%lld",a+i);
    }
    sort(a+1,a+n+1);
    reverse(a+1,a+n+1);
    for(int i=0;i<=m;i++){
        for(int j=0;j<=n;j++){
            dp[i][j]=INF;
        }
    }
    dp[0][0]=0;
    for(int i=1;i<=m;i++){
        top=0;
        stk[0]=0;
        for(int j=1;j<=n;j++){
            while(top>0&&dp[i-1][stk[top]]-stk[top]*a[j]>dp[i-1][stk[top-1]]-stk[top-1]*a[j])top--;
            dp[i][j]=dp[i-1][stk[top]]+(j-stk[top])*a[j];
            while(top>0&&(dp[i-1][stk[top]]-dp[i-1][j])*(stk[top]-stk[top-1])>(dp[i-1][stk[top-1]]-dp[i-1][stk[top]])*(j-stk[top]))top--;
            stk[++top]=j;
        }
    }
    int ans=INF;
    for(int i=m;i<=n;i++)ans=min(ans,dp[m][i]);
    printf("%lld\n",ans);
}
signed main()
{
    freopen("protect.in","r",stdin);
    freopen("protect.out","w",stdout);
    int T;
    scanf("%lld",&T);
    while(T--)solve();
    return 0;
}

C. Wall

经典线段树。维护区间 \(\max\)\(\min\) 的 tag,具体 pushdown 见代码。

点击查看代码
#pragma GCC optimize(3)
#include <bits/stdc++.h>

using namespace std;

const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;

int n, m;
int mx[N << 2], mn[N << 2];

void tagMax(int x, int v) {
    mx[x] = max(mx[x], v);
    mn[x] = max(mn[x], v);
}

void tagMin(int x, int v) {
    mx[x] = min(mx[x], v);
    mn[x] = min(mn[x], v);
}

void pushdown(int x) {
    if(mx[x] > 0) {
        tagMax(x << 1, mx[x]);
        tagMax(x << 1 | 1, mx[x]);
        mx[x] = 0;
    }
    if(mn[x] < INF) {
        tagMin(x << 1, mn[x]);
        tagMin(x << 1 | 1, mn[x]);
        mn[x] = INF;
    }
}

void updateMax(int x, int l, int r, int ql, int qr, int v) {
    if(l >= ql && r <= qr) {
        tagMax(x, v);
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(ql <= mid) updateMax(x << 1, l, mid, ql, qr, v);
    if(qr > mid) updateMax(x << 1 | 1, mid + 1, r, ql, qr, v);
}

void updateMin(int x, int l, int r, int ql, int qr, int v) {
    if(l >= ql && r <= qr) {
        tagMin(x, v);
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(ql <= mid) updateMin(x << 1, l, mid, ql, qr, v);
    if(qr > mid) updateMin(x << 1 | 1, mid + 1, r, ql, qr, v);
}

void print(int x, int l, int r) {
    if(l == r) {
        cout << mx[x] << "\n";
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    print(x << 1, l, mid);
    print(x << 1 | 1, mid + 1, r);
}

int main() {
    freopen("wall.in", "r", stdin);
    freopen("wall.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    memset(mn, 0x3f, sizeof(mn));
    while (m--) {
        int op, l, r, h;
        cin >> op >> l >> r >> h;
        ++l, ++r;
        if(op == 1) updateMax(1, 1, n, l, r, h);
        else updateMin(1, 1, n, l, r, h);
    }
    print(1, 1, n);
    return 0;
}

D. 控制

妙妙题。先考虑 \(n=2\) 的怎么做,发现一种用 priority_queue 维护的做法,推广到 \(n>2\) 的做法即可。详情见代码。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define F first
#define S second
#define pb push_back
#define mpr make_pair
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,r,l) for(int i=(r);i>=(l);i--)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int B=23333,MOD=20190816170251;
int n,m,len[100010];
vector<int> a[100010];
priority_queue<pair<int,pair<int,int>>> pq;
bool cmp(vector<int> x,vector<int> y){
    return x[1]-x[2]<y[1]-y[2];
}
signed main()
{
    freopen("contain.in","r",stdin);
    freopen("contain.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    int sum=0;
    for(int i=1;i<=n;i++){
        scanf("%lld",len+i);
        a[i].clear();
        a[i].push_back(0);
        for(int j=1;j<=len[i];j++){
            int x;
            scanf("%lld",&x);
            a[i].push_back(x);
        }
        if(len[i]==1){
            sum+=a[i][1];
            i--;
            n--;
            continue;
        }
        sort(a[i].begin()+1,a[i].end(),greater<int>());
        sum+=a[i][1];
    }
    if(!n){
        printf("%lld\n",sum);
        return 0;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)len[i]=(int)a[i].size()-1;
    int ans=sum;
    pq.push({sum+a[1][2]-a[1][1],{1,2}});
    while(!pq.empty()&&(--m)){
        int curS=pq.top().first,rowid=pq.top().second.first,colid=pq.top().second.second;
        pq.pop();
        ans=(ans*B%MOD+curS)%MOD;
        if(colid<len[rowid]){
            pq.push({curS+a[rowid][colid+1]-a[rowid][colid],{rowid,colid+1}});
        }
        if(rowid<n){
            int nwS=curS+a[rowid+1][2]-a[rowid+1][1];
            pq.push({nwS,{rowid+1,2}});
            if(colid==2){
                pq.push({nwS-a[rowid][2]+a[rowid][1],{rowid+1,2}});
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

2023.10.14 NOIP2023-div2模拟赛19

A. 超速

题意不清。垃圾题目。

点击查看代码
#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 15, M = 1e5 + 10;

int n, v[N], l[N];
int m, a[M], f[M];
int q, L;

bool check(int x) {
    if(x == m) return 1;
    long double sum = 0.0;
    for(int i = 1; i <= n; i++) sum += (long double)l[i] / (long double)(v[i] + a[x]);
    return sum <= (long double)L;
}

signed main() {
    freopen("speed.in", "r", stdin);
    freopen("speed.out", "w", stdout);
    scanf("%lld", &n);
    for(int i = 1; i <= n; i++) scanf("%lld", v + i);
    for(int i = 1; i <= n; i++) scanf("%lld", l + i);
    scanf("%lld", &m);
    for(int i = 1; i < m; i++) scanf("%lld", a + i);
    for(int i = 1; i <= m; i++) scanf("%lld", f + i);
    scanf("%lld", &q);
    while(q--) {
        int s, t;
        scanf("%lld%lld", &s, &t);
        L = t - s;
        int l = 0, r = m;
        while(l < r) {
            int mid = (l + r) >> 1;
            if(check(mid)) r = mid;
            else l = mid + 1;
        }
        printf("%lld\n", f[l]);
    }
    return 0;
}

B. 对常规的斗争

经典题。考虑每一个数作为区间中第一个同类的数的贡献,即求哪些区间以这个数作为第一个这一类的数。容易发现规律,二次差分即可。

点击查看代码
#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 2e5 + 10;

int n, a[N], lst[N], c[N];
vector<int> vec;

signed main() {
    freopen("fight.in", "r", stdin);
    freopen("fight.out", "w", stdout);
    scanf("%lld", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
        vec.push_back(a[i]);
    }
    sort(vec.begin(), vec.end());
    vec.resize(unique(vec.begin(), vec.end()) - vec.begin());
    for(int i = 1; i <= n; i++) a[i] = lower_bound(vec.begin(), vec.end(), a[i]) - vec.begin() + 1;
    for(int i = 1; i <= n; i++) {
        c[1]++;
        c[i - lst[a[i]] + 1]--;
        c[n - i + 2]--;
        c[i - lst[a[i]] + n - i + 2]++;
        lst[a[i]] = i;
    }
    for(int i = 1; i <= n; i++) c[i] += c[i - 1];
    for(int i = 1; i <= n; i++) c[i] += c[i - 1];
    for(int i = 1; i <= n; i++) printf("%lld ", c[i]);
    return 0;
}

C. 海报

考虑暴力的 dp。设 \(f(i,j)\) 表示考虑到 \(i\),已经连续选了 \(j\) 数了的最大和。因为是环,所以需要枚举最后一次选了多少个。

这个东西显然能用矩阵维护,线段树直接搞!

点击查看代码
#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 4e4 + 10;

int n, a[N], q;
int dp[N][4];

struct Matrix {
    int a[4][4], N, M;

    Matrix() {memset(a, -0x3f, sizeof(a));}

    Matrix operator * (const Matrix &p) const {
        Matrix res;
        res.N = N, res.M = p.M;
        for(int i = 0; i <= res.N; i++) {
            for(int j = 0; j <= res.M; j++) {
                for(int k = 0; k <= M; k++) {
                    res.a[i][j] = max(res.a[i][j], a[i][k] + p.a[k][j]);
                }
            }
        }
        return res;
    }

    void rset(int v) {
        N = M = 3;
        a[0][0] = a[1][0] = a[2][0] = a[3][0] = 0;
        a[0][1] = a[1][2] = a[2][3] = v;
        // for(int i = 0; i <= 3; i++) {
        //     for(int j = 0; j <= 3; j++) {
        //         cerr << a[i][j] << " ";
        //     }
        //     cerr << endl;
        // }
    }
} val[N << 2], emp;

struct SegmentTree {
    void pushup(int x) {val[x] = val[x << 1] * val[x << 1 | 1];}

    void build(int x, int l, int r) {
        if(l == r) {
            val[x].rset(a[l]);
            return;
        }
        int mid = (l + r) >> 1;
        build(x << 1, l, mid);
        build(x << 1 | 1, mid + 1, r);
        pushup(x);
    }

    void update(int x, int l, int r, int pos, int v) {
        if(l == r) {
            val[x].rset(v);
            return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) update(x << 1, l, mid, pos, v);
        else update(x << 1 | 1, mid + 1, r, pos, v);
        pushup(x);
    }

    Matrix query(int x, int l, int r, int ql, int qr) {
        if(ql > qr) return emp;
        if(l >= ql && r <= qr) return val[x];
        int mid = (l + r) >> 1;
        if(ql > mid) return query(x << 1 | 1, mid + 1, r, ql, qr);
        if(qr <= mid) return query(x << 1, l, mid, ql, qr);
        return query(x << 1, l, mid, ql, qr) * query(x << 1 | 1, mid + 1, r, ql, qr);
    }
} T;

int calc() {
    int res = 0;
    for(int x = 0; x <= 3; x++) {
        int sum = 0;
        for(int i = n; i >= n - x + 1; i--) sum += a[i];
        Matrix cur;
        cur.N = 0, cur.M = 3;
        cur.a[0][x] = sum;
        cur = cur * T.query(1, 1, n, 1, n - x - 1);
        for(int i = 0; i <= 3; i++) res = max(res, cur.a[0][i]);
        // memset(dp, 0, sizeof(dp));
        // dp[1][x + 1] = sum + a[1];
        // dp[1][0] = sum;
        // res = max(res, dp[1][x + 1]);
        // for(int i = 2; i < n - x; i++) {
        //     int trans = 0;
        //     for(int j = 1; j <= 3; j++) dp[i][j] = dp[i - 1][j - 1] + a[i];
        //     dp[i][0] = 0;
        //     for(int j = 0; j <= 3; j++) dp[i][0] = max(dp[i][0], dp[i - 1][j]);
        //     for(int j = 0; j <= 3; j++) res = max(res, dp[i][j]);
        // }
    }
    return res;
}

signed main() {
    freopen("poster.in", "r", stdin);
    freopen("poster.out", "w", stdout);
    scanf("%lld", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
    }
    T.build(1, 1, n);
    printf("%lld\n", calc());
    scanf("%lld", &q);
    while(q--) {
        int pos, v;
        scanf("%lld %lld", &pos, &v);
        a[pos] = v;
        T.update(1, 1, n, pos, v);
        printf("%lld\n", calc());
    }
    return 0;
}

D. 机器人锦标赛

难度在题意理解/cf。可以发现前面一段的阈值填 \(m\) 一定可以,所以二分最多能填多少个 \(m\),然后再确定下一位即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int n,m,s,z[N];
bool val[N<<1];
vector<int> a[N];
vector<pair<pair<int,int>,int>> vec[N];
int calc(){
    int cnt=0;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++)val[j]=(a[i][j]<z[j]);
        for(int j=1;j<n;j++){
            int x=vec[i][j-1].first.first,y=vec[i][j-1].first.second,op=vec[i][j-1].second;
            if(op==1)val[n+j]=(val[x]&val[y]);
            else val[n+j]=(val[x]|val[y]);
        }
        cnt+=val[n+n-1];
    }
    return cnt;
}
int main(){
    freopen("robot.in","r",stdin);
    freopen("robot.out","w",stdout);
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        for(int j=0;j<n-1;j++){
            int a,b,op;
            cin>>a>>b>>op;
            vec[i].push_back(make_pair(make_pair(a,b),op));
        }
    }
    for(int i=1;i<=m;i++){
        a[i].push_back(0);
        for(int j=1;j<=n;j++){
            int x;cin>>x;
            a[i].push_back(x);
        }
    }
    int l=1,r=n+1;
    while(l<r){
        int mid=(l+r+1)>>1;
        memset(z,0,sizeof(z));
        for(int i=1;i<mid;i++)z[i]=m;
        if(calc()<=s)l=mid;
        else r=mid-1;
    }
    memset(z,0,sizeof(z));
    for(int i=1;i<l;i++)z[i]=m;
    int L=0,R=m;
    while(L<R){
        int mid=(L+R+1)>>1;
        z[l]=mid;
        if(calc()<=s)L=mid;
        else R=mid-1;
    }
    z[l]=L;
    for(int i=1;i<=n;i++)cout<<z[i]<<" ";
    return 0;
}

gugugu 了好几天。

2023.10.18 NOIP2023-div2模拟赛22

A. 玛雅历

shaber 模拟,不做评价。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
#define ll long long
#define vi vector<int>
#define sz(a) ((int)(a.size()))
#define mset(f, x) memset(f, x, sizeof(f))
#define ALL(x) (x).begin(), (x).end()
#define rALL(x) (x).rbegin(), (x).rend()
#define uni(x) x.resize(unique(ALL(x)) - x.begin())
#define ull unsigned long long
#define pii pair<int, int>
using namespace std;
const int Times[9] = {1, 20, 360, 7200, 144000, 2880000, 57600000, 1152000000, 23040000000};
const int Months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int a[9];
bool is_leap_year(int Y) {return (Y % 4 == 0 && (Y % 100 || Y % 400 == 0));}
void solve() {
    scanf("%lld.%lld.%lld.%lld.%lld.%lld.%lld.%lld.%lld", a + 8, a + 7, a + 6, a + 5, a + 4, a + 3, a + 2, a + 1, a + 0);
    int days = 0;
    rep(i, 0, 8) days += a[i] * Times[i];
    int Y = (days / 146097) * 400 - 3113, M = 8, D = 11;
    days %= 146097;
    while(days >= 366) {
        days -= 365;
        Y++;
        if(is_leap_year(Y)) days--;
    }
    while(days--) {
        D++;
        int x = Months[M];
        if(M == 2 && is_leap_year(Y)) x++;
        if(D > x) {
            D = 1;
            M++;
            if(M > 12) {
                M = 1;
                Y++;
            }
        }
    }
    if(Y <= 0) printf("%lld %lld %lld BC\n", D, M, 1 - Y);
    else printf("%lld %lld %lld\n", D, M, Y);
}
signed main() {
    freopen("maya.in", "r", stdin);
    freopen("maya.out", "w", stdout);
    int cas;
    scanf("%lld", &cas);
    while(cas--) {
        solve();
    }
    return 0;
}

B. 大融合

Hint: 二分 + DP + 单调队列优化。

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
#define ll long long
#define vi vector<int>
#define sz(a) ((int)(a.size()))
#define mset(f, x) memset(f, x, sizeof(f))
#define ALL(x) (x).begin(), (x).end()
#define rALL(x) (x).rbegin(), (x).rend()
#define uni(x) x.resize(unique(ALL(x)) - x.begin())
#define ull unsigned long long
#define pii pair<int, int>
using namespace std;
const int N = 1e3 + 10, INF = 0x3f3f3f3f3f3f3f3f;
int n, m, Q, d, c[N], Lim = 0;
int x[N][N], f[N][N], dp[N][N];
unordered_map<int, bool> app;
deque<pii> q[N][2], tmp[2];
bool check(int v) {
    int L = max(1LL, d - v), R = min(n, d + v);
    rep(i, 1, n) rep(j, 1, n) dp[i][j] = -INF; dp[1][1] = 0;
    int ans = 0;
    rep(i, 1, n) rep(t, 0, 1) while(!q[i][t].empty()) q[i][t].pop_back();
    rep(i, 1, n) {
        rep(t, 0, 1) while(!tmp[t].empty()) tmp[t].pop_back();
        rep(j, 1, n) {
            rep(t, 0, 1) {
                while(!tmp[t].empty() && tmp[t].front().second < j - R) tmp[t].pop_front();
                while(!q[j][t].empty() && q[j][t].front().second < j - R) q[j][t].pop_front();
            }
            if(j > L) {
                int cur = x[i][j - L];
                while(!tmp[cur].empty() && tmp[cur].back().first <= dp[i][j - L]) tmp[cur].pop_back();
                tmp[cur].push_back(make_pair(dp[i][j - L], j - L));
            }
            if(i > L) {
                int cur = x[i - L][j];
                while(!q[j][cur].empty() && q[j][cur].back().first <= dp[i - L][j]) q[j][cur].pop_back();
                q[j][cur].push_back(make_pair(dp[i - L][j], i - L));
            }
            rep(t, 0, 1) {
                int delta = f[i][j] + (x[i][j] == t);
                if(!tmp[t].empty()) dp[i][j] = max(dp[i][j], tmp[t].front().first + delta);
                if(!q[j][t].empty()) dp[i][j] = max(dp[i][j], q[j][t].front().first + delta);
            }
            ans = max(ans, dp[i][j]);
        }
    }
    return Lim <= ans;
}
signed main() {
    freopen("harbinbeer.in", "r", stdin);
    freopen("harbinbeer.out", "w", stdout);
    scanf("%lld %lld %lld %lld", &n, &m, &Q, &d);
    rep(i, 1, m) {
        scanf("%lld", c + i);
        int pw = 1;
        while(pw <= c[i]) {
            app[c[i] % (pw * 10)] = 1;
            pw *= 10;
        }
    }
    while(Q--) {
        int l, b, w;
        scanf("%lld %lld %lld", &l, &b, &w);
        if(!app[b]) Lim += w;
    }
    rep(i, 1, n) rep(j, 1, n) scanf("%lld", &x[i][j]), x[i][j]--;
    rep(i, 1, n) rep(j, 1, n) scanf("%lld", &f[i][j]);
    if(!check(n)) {
        printf("-1\n");
        return 0;
    }
    int l = 0, r = n;
    while(l < r) {
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    } printf("%lld\n", l);
    return 0;
}

C. 好 ♂ 朋 ♂ 友

性质题。

观察到对于每个左端点,区间 \(\texttt{OR}\) 的值只有可能有 \(O(\log V)\) 个。

那就区间离线下来,线段树维护即可。

upd:我好像是我们年级这一题唯一没有被卡常的!!!

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
#define ll long long
#define vi vector<int>
#define sz(a) ((int)(a.size()))
#define mset(f, x) memset(f, x, sizeof(f))
#define ALL(x) (x).begin(), (x).end()
#define rALL(x) (x).rbegin(), (x).rend()
#define uni(x) x.resize(unique(ALL(x)) - x.begin())
#define ull unsigned long long
#define pii pair<int, int>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m, k, a[N], st[N][20], ans[M];
bool can[10];
struct SegmentTree {
    int sum[N << 2], tag[N << 2];
    void pushup(int x) {sum[x] = sum[x << 1] + sum[x << 1 | 1];}
    void pushdown(int x, int l, int r) {
        if(!tag[x]) return;
        int mid = (l + r) >> 1;
        sum[x << 1] += tag[x] * (mid - l + 1);
        sum[x << 1 | 1] += tag[x] * (r - mid);
        tag[x << 1] += tag[x];
        tag[x << 1 | 1] += tag[x];
        tag[x] = 0;
    }
    void update(int x, int l, int r, int ql, int qr) {
        if(l >= ql && r <= qr) {
            sum[x] += r - l + 1;
            tag[x]++;
            return;
        }
        pushdown(x, l, r);
        int mid = (l + r) >> 1;
        if(ql <= mid) update(x << 1, l, mid, ql, qr);
        if(qr > mid) update(x << 1 | 1, mid + 1, r, ql, qr);
        pushup(x);
    }
    int query(int x, int l, int r, int ql, int qr) {
        if(l >= ql && r <= qr) return sum[x];
        pushdown(x, l, r);
        int mid = (l + r) >> 1, res = 0;
        if(ql <= mid) res = query(x << 1, l, mid, ql, qr);
        if(qr > mid) res += query(x << 1 | 1, mid + 1, r, ql, qr);
        return res;
    }
} T;
struct Node {
    int id, l, r;
    bool operator < (const Node &p) const {
        return r < p.r;
    }
} q[M];
signed main() {
    freopen("friend.in", "r", stdin);
    freopen("friend.out", "w", stdout);
    scanf("%lld %lld %lld", &n, &m, &k);
    rep(i, 1, k) {
        int x;
        scanf("%lld", &x);
        can[x] = 1;
    }
    rep(i, 1, n) {
        scanf("%lld", a + i);
        st[i][0] = (a[i] | a[i - 1]);
    }
    rep(j, 1, 19) {
        rep(i, 1, n) {
            st[i][j] = (st[i][j - 1] | st[max(0LL, i - (1 << (j - 1)))][j - 1]);
        }
    }
    rep(i, 1, m) {
        scanf("%lld %lld", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + m + 1);
    q[0].r = 0;
    rep(i, 1, m) {
        rep(j, q[i - 1].r + 1, q[i].r) {
            int pos = j, val = a[j];
            while(pos >= 1) {
                int nxt = pos;
                per(k, 19, 0) {
                    if(nxt > (1 << k)) {
                        if((val | st[nxt][k]) != val) continue;
                        nxt -= (1 << k);
                    }
                }
                if(can[val % 10]) T.update(1, 1, n, nxt, pos);
                pos = nxt - 1, val |= a[pos];
            }
        }
        ans[q[i].id] = T.query(1, 1, n, q[i].l, q[i].r);
    }
    rep(i, 1, m) printf("%lld\n", ans[i]);
    return 0;
}

D. 图的直径

gugugu.

posted @ 2023-09-18 13:33  Jerry_Jiang  阅读(39)  评论(1编辑  收藏  举报