xcpc区域赛训练记录

有训练记录在之前的寒假复健随笔里

因为寒假过了所以开了个新随笔。

ICPC2020济南

C stone game

全部转成在模3意义下

发现把12尽量转化成3是最优的

然后就先让12配对,剩下单独的12再自己想办法凑成3

复制代码
#include <bits/stdc++.h>
using namespace std;
long long a,b,c,ans;
int main(){
    scanf("%lld%lld%lld",&a,&b,&c);
    if (a>=b){
        long long y=a-b;
        ans=ans+2ll*b;
        ans=ans+(y/3)*3;
        long long t=y%3;
        if (t==2) ans++;
    }
    else{
        long long y=b-a;
        ans=ans+2ll*a;
        ans=ans+(y/3)*6ll;
        long long t=y%3;
        if (t==2) ans=ans+4ll;
    }
    cout<<ans;
    return 0;
}
复制代码

D.Fight against involution

合理内卷(

最native的想法肯定是全取li,但是显然不行,因为可能会导致排名发生变化

于是我们按照ri先排序,这样得到的就是最高排名

然后对于每个位置,它至少要取到前面的li中的max来保证自己的排名,因为所有人都是最轻松的选择

直接模拟即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
long long ans;
struct Node{
    long long l,r;
}a[100005];
int temp(Node a,Node b){
    return (a.r<b.r||(a.r==b.r)&&(a.l>b.l));
}
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
     scanf("%lld%lld",&a[i].l,&a[i].r);
    sort(a+1,a+N+1,temp);
    long long nw=0;
    for (int i=1;i<=N;i++){
        if (a[i].l>nw) nw=a[i].l;
        ans+=nw;
    }
    cout<<ans;
    return 0;
}
复制代码

L.Bit Sequence

如果没有+m的限制是个显然的数位dp

但是有+m

有点像之前dls讲的一个题

低位不影响高位

所以我们先把前128个数字暴力拿出来,+m,然后把这个数字拆成左右半边考虑,左边数位dp统一统计一下即可。

代码是学长写的

复制代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[64][2][128][2][2];
int v[65],a[105];
int m;
ll cal(int st,int s,int t)
{
    int f=1;
    for(int i=0;i<m&&f;i++)
    {
        if(st+i<128) f&=(__builtin_parity(st+i)^s)==a[i];
        else f&=(__builtin_parity(st+i)^s^t)==a[i];
    }
    return f;
}
ll dfs(int pos,int limit,int st,int s,int t)
{
    if(pos==-1) return cal(st,s,t) ;
    ll &res=dp[pos][limit][st][s][t];
    if(res!=-1) return res;
    res=0;
    int up=limit?v[pos]:1;
    for(int i=0;i<=up;i++)
    if(pos>6) res+=dfs(pos-1,limit&i==up,(st*2+i)&127,s^i,i&(!t));
    else res+=dfs(pos-1,limit&i==up,(st*2+i)&127,s,t);
    //cout<<res<<'\n';
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
    {
        ll L;
        cin>>m>>L;
        for(int i=0;i<m;i++) cin>>a[i];
        memset(dp,-1,sizeof dp);
        int len=0;
        for(;L;L>>=1)v[len++]=L&1;

        cout<<dfs(len-1,1,0,0,0)<<'\n';
    }

}
复制代码

 赛后补题:

A.atrix Equation

草了,我高消的板子有问题

发现操作2可以转化成xor,操作1可以转化成or

于是我们可以有一个暴力的想法,解n2个异或方程组

然后仔细思考,不难发现同一列每n个方程组是一组的,不涉及其他列的变量

于是就变成解n次,一次n个方程组

这东西可以直接n4/64bitset优化一下即可。

高消板子有问题调了大半场没出,麻了

 

复制代码
#include <bits/stdc++.h>
using namespace std;
const long long fish=998244353;
bitset<205> a[205];
int aa[205][205],b[205][205];
long long anss=0;
int N; 
long long Pow(long long x,long long  y){
    long long ans=1;
    while (y){
        if (y&1ll) ans=1ll*ans*x%fish;
        x=1ll*x*x%fish;
        y>>=1ll;
    }
    return ans;
}
long long Gauss(long long n){
    int t=1;
    long long now=1,ans=0;
    for (int i=1;i<=n;i++){
        now=t;
        while (!a[now][i]&&now<=n)
            now++;
        if (now==n+1) continue;
        if (now!=t) swap(a[now],a[t]);
        for (int j=t+1;j<=n;j++){
            if (a[j][i]) a[j]^=a[t];
        }
        ans++;
        t++;
    }
    return n-ans;
}
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        for (int j=1;j<=N;j++)
            scanf("%d",&aa[i][j]);
    for (int i=1;i<=N;i++)
        for (int j=1;j<=N;j++)
            scanf("%d",&b[i][j]);
    for (int i=1;i<=N;i++){
        for (int j=1;j<=N;j++){
            for (int k=1;k<=N;k++){
                a[j][k]=aa[j][k];
            }        
            a[j][j]=(aa[j][j]-b[j][i]+2)%2;
        }    
        long long T=Gauss(1ll*N);
        anss+=T;
    }
    cout<<Pow(2ll,anss);
    return 0;
}
复制代码

 2019ccpc秦皇岛

D. Decimal

 签到,判断这个数的因子是不是只有2/5即可

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N; 
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        while (N%2==0) N/=2;
        while (N%5==0) N/=5;
        if (N!=1) printf("Yes\n");
        else printf("No\n"); 
    }
    return 0;
}
复制代码

F. Forest Program

分类讨论。

首先把所有环取出来

发现其实要是森林的话,不能有环

其实就是每个环至少断一个边

对一个有n个边的环来说

这个答案是2n1

剩下的边随便取

2cnt

怎么找环?

类似tarjan,dfs的时候找到一个访问过的位置大力弹栈就行

因为仙人掌图所以显然是正确的。

代码学长写的

复制代码
#include <bits/stdc++.h>
using namespace std;
const int P= 998244353;
const int N=1e6+10;
int ans=1,cnt=0;
vector<int>E[N];
int vis[N],pre[N];
int power(int x,int y){
        int res=1;
        while(y)
        {
            if (y&1) res=1LL*res*x%P;
            x=1LL*x*x%P;
            y>>=1;
        }
        return res;
    }
void dfs(int u,int f){
           vis[u]=1;
          for(auto v:E[u])
          {
              if(v==f||vis[v]==2) continue;
            if(vis[v]==1)
            {
               int now=u;
               int sz=1;
               while(now!=v)now=pre[now],sz++;
               cnt+=sz;
               ans=(1LL*ans*(power(2,sz)-1)%P+P)%P; 
            }else pre[v]=u,dfs(v,u);
          }
          vis[u]=2;
 }
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,m;
    cin>>n>>m;
 
    for(int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0);
    ans=1LL*ans*power(2,m-cnt)%P;
    cout<<ans<<'\n';
}
复制代码

K. MUV LUV UNLIMITED

很有趣的博弈题.jpg

首先,如果一个树全是由偶数链构成,那么显然后手必胜

全是奇数链则先手必胜

考虑奇偶交替的情况

显然,先手可以决策局势

因为我可以把所有的奇数取成偶数

然后就有一个native的想法, 大力考虑所有叶子节点到根的距离的奇偶性,然后全是偶数才后手必胜,否则先手必胜

然后被样例叉出去了(

为什么?

因为路径会有交叉

那么我们就每次考虑一个子问题,即每次考虑一个点,下面挂的全是链

显然这个子情况符合我上面说的东西

那么我们要怎么合并子情况?

实际上还是只有全是偶数的时候后手必胜,因为这时候后手在每一轮子问题里都必胜

否则先手是可以决定到一个子问题的终点的时候自己是先手还是后手的,而这会决定下一个子问题的胜负。

于是做法就是:对所有子问题考虑,如果路径全是偶数的话,那么后手胜利

否则先手必胜。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,T;
int odd,even;
vector<int> Son[1000005];
void dfs(int Now,int Len,int dep){
    if (Son[Now].size()==0){
        int Lenn=Len-dep;
        if (Lenn&1) odd=1;
        return;
    }
    for (auto x:Son[Now]){
        if (Son[Now].size()>1)
        dfs(x,Len+1,Len);
        else dfs(x,Len+1,dep);
        if (odd) return;
    }
}
int main(){
    scanf("%d",&T);
    while (T--){
        odd=0,even=0;
        scanf("%d",&N);
        for (int i=1;i<=N;i++)
             Son[i].clear();
        for (int i=2;i<=N;i++){
            int fa;
            scanf("%d",&fa);
            Son[fa].push_back(i);
        }
        dfs(1,1,0);
        if (!odd) {
            printf("Meiya\n");
        }
        else printf("Takeru\n");
    }
    return 0;
}
复制代码

 赛后补题:

E.Escape

核心是发现至多两个机器人经过同一个位置,且这两个机器人的方向一定不同(可以画画图或者反证)

然后发现n<=100,我们考虑网络流

把一个位置拆成两个点,横点和竖点

每个点可以向四个方向连一个流量为1的边,表示正常走路

横点竖点间连一个流量为1的边,表示换向。

然后每个终点向T连边,流量为1S向起点,流量为1

代码是赛中队友写的,赛后调了调

赛中没考虑终点能否到达,而且到终点的流量连成了inf

复制代码
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N=1e5+10,M=1e6+10,INF=1e9;
int n,m,S,T,cnt;
int head[N],d[N],cur[N];
struct edges
{
    int v,nxt,f;
}e[M*2];
void add(int u,int v,int f)
{
    e[cnt].v=v,e[cnt].nxt=head[u],e[cnt].f=f,head[u]=cnt++;
    e[cnt].v=u,e[cnt].nxt=head[v],e[cnt].f=0,head[v]=cnt++;
}
 
bool bfs()
{
    memset(d,-1,sizeof d);
    queue<int>q;
    q.push(S);
    d[S]=0,cur[S]=head[S];
    while(q.size())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].v,f=e[i].f;
            if(d[v]==-1&&f)
            {
                d[v]=d[u]+1;
                cur[v]=head[v];
                if(v==T) return true;
                q.push(v);
            }
        }
    }
    return false;
}
int find(int u,int limit)
{
    if(u==T) return limit;
    int flow=0;
    for(int i=cur[u];~i&&flow<limit;i=e[i].nxt)
    {
        cur[u]=i;
        int v=e[i].v,f=e[i].f;
        if(d[v]==d[u]+1&&f)
        {
            int t=find(v,min(f,limit-flow));
            if(!t) d[v]=-1;
            e[i].f-=t,e[i^1].f+=t,flow+=t;
        }
    }
    return flow;
}
int dinic()
{
    int ans=0,flow;
    while(bfs()) while(flow=find(S,INF)) ans+=flow;
    return ans;
}
int x[105][105],y[105][105];
char g[105][105];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin>>t;
    while(t--)
    {
        memset(head,-1,sizeof head);
        cnt=0;
        int a,b;
        cin>>n>>m>>a>>b;
        S=0,T=n*m*2+1;
        int idx=0;
        for(int i=1;i<=n;i++)
        cin>>g[i]+1;
 
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++) x[i][j]=++idx,y[i][j]=++idx;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                if(g[i][j]=='1') continue; 
                if(j-1>0&&g[i][j-1]=='0') add(x[i][j],x[i][j-1],1);
                if(j+1<=m&&g[i][j+1]=='0') add(x[i][j],x[i][j+1],1);
                if(i-1>0&&g[i-1][j]=='0') add(y[i][j],y[i-1][j],1);
                if(i+1<=n&&g[i+1][j]=='0') add(y[i][j],y[i+1][j],1);
                add(x[i][j],y[i][j],1);
                add(y[i][j],x[i][j],1);
            }
        for(int i=1;i<=a;i++) 
        {
            int x;
            cin>>x;
            if(g[1][x]=='1') continue;
            add(S,y[1][x],1);
        }
        for(int i=1;i<=b;i++)
        {
            int x;
            cin>>x;
            if(g[n][x]=='1') continue;
            add(y[n][x],T,1);
        }
    
        if(dinic()<a) cout<<"No";
        else cout<<"Yes";
        cout<<'\n';
    }
    
    
复制代码

 

G. Game on Chessboard
我不理解(
为什么TLE  on test6 卡了半天常数过不去,重构一下过了
 
发现n非常的小,考虑状压
然后这种左下角全空的考虑插头dp
转移应该挺裸的,枚举一个位置或者两个位置一起消
重点是要发现优先两个一起消去
复制代码
#include <bits/stdc++.h>
using namespace std;
int dp[1<<24],N;
int w[12][12];
char a[12][12];
int main(){
    scanf("%d",&N);
    for (int i=0;i<N;i++)
        scanf("%s",a[i]);
    for (int i=0;i<N;i++)
        for (int j=0;j<N;j++)
            scanf("%d",&w[i][j]);
    int T=(1<<N)-1;
    int S=(T<<N);
    for (int i=T;i<=S;i++) dp[i]=1e9;
    dp[S]=0;
    for (int st=S;st>T;st--){
        if (dp[st]==1e9) continue;
        int cnt=0;
        for (int i=0;i<=2*N;i++)
            if (st>>i&1) cnt++;
        if (cnt!=N) continue;
        int x=-1,y=0;
        for (int i=2*N-1;i>=1;i--){
            if ((st>>i)&1) x++;
            else y++;
            if ((st>>i)&1 && !((st>>(i-1))&1)){
                dp[st-(1<<(i-1))]=min(dp[st-(1<<(i-1))],dp[st]+(a[x][y]!='.')*w[x][y]);
                int nx=-1,ny=0;
                for (int j=2*N-1;j>i+1;j--){
                    if ((st>>j)&1) nx++;
                        else ny++;
                    if (((st>>j)&1) && (!((st>>(j-1))&1)) && (a[x][y] + a[nx][ny] == 'B' + 'W')){
                        dp[st-(1<<(i-1))-(1<<(j-1))]=min(dp[st-(1<<(i-1))-(1<<(j-1))],dp[st]+abs(w[x][y]-w[nx][ny]));
                    }
                }
            }
        }
    }
    cout<<dp[T];
    return 0;
}
复制代码

 icpc2020首尔

B. Commemorative Dice

签到,暴力枚举

C. Dessert Café

因为两点之间在树上只有一条路径,稍微画画图就会发现答案的点其实就是关键点之间的点。

复制代码
#include <bits/stdc++.h>
using namespace std;
int ans=0;
int N,K;
int pd[100005],cnt,Arrive[200005],nex[200005],las[200005];
bool col[200005];
void dfs(int Now,int fa){
    int cnt=0,cnt1=0;
    for (int i=las[Now];i;i=nex[i]){
        int v=Arrive[i];
        if (v==fa) continue;
        dfs(v,Now);
        pd[Now]+=pd[v];
        if (pd[v]) cnt++;
    }
    if (col[Now]) pd[Now]++;
    if (K-pd[Now]!=0) cnt++;
    if (cnt>=2||col[Now]) ans++;
}
void jt(int x,int y){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
}
int main(){
    scanf("%d%d",&N,&K);
    for (int i=1;i<N;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        jt(u,v);
        jt(v,u); 
    }
    for (int i=1;i<=K;i++){
        int x;
        scanf("%d",&x);
        col[x]=true;
    }
    dfs(1,1);
    printf("%d\n",ans);
    return 0;
}
复制代码

G. Mobile Robot

固定起点后,每个点都确定了,每个点还是会走到它对应的那个位置上去

考虑从起点向右延伸,则对于每个点来说,它走的距离就是|x+(i1)dposi|

x是我们的起点

|x(posi(i1)d)|

最小化这个的最大值

我们转化一下,其实就是有n个新点,每个点是posi(i1)d,找一个点x到最离它远点距离最近

显然是区间中点。

左延伸类似的做。

注意精度。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
long long pos[1000005],d;
long long pos1[1000005],pos2[1000005];
int main(){
    scanf("%d%lld",&N,&d);
    for (int i=1;i<=N;i++){
        scanf("%lld",&pos[i]);
        pos1[i]=pos[i]-(i-1)*d;
        pos2[i]=pos[i]+(i-1)*d;
    }
    sort(pos1+1,pos1+N+1);
    sort(pos2+1,pos2+N+1); 
    long long ans1=0;
    ans1=(pos1[N]-pos1[1])/2;
    long long anss=(pos2[N]-pos2[1])/2;
    long long Ans;
    int flag=0;
    if (anss<ans1){
        Ans=anss;
        if ((pos2[N]-pos2[1])%2==0) flag=0;
        else flag=1;
    }
    else{
        Ans=ans1;
        if ((pos1[N]-pos1[1])%2==0) flag=0;
        else flag=1;
    }
    printf("%lld.%d\n",min(anss,ans1),flag*5);
    return 0;
}
复制代码

H. Needle

转化一下式子,统计2x2=x1+x3的三元组(x1,x2,x3)

考虑固定2x2

f(x)g(y),xy=2x2

fft即可。

复制代码
//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;

const int N = 1100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809 
const int M=18e5;
const int mod=998244353;
template <typename T>void rd(T &x)
{
    x = 0;
     int f = 1;
     char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
    x *= f;
}

ll qpow(ll a, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}

namespace Poly
{
    #define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
    #define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
    #define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
    #define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了

    typedef vector<ll> poly;
    const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
    //一般模数的原根为 2 3 5 7 10 6
    const int inv_G = qpow(G, mod - 2);
    int RR[N], deer[2][21][N], inv[N];

    void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
        for(int p = 1; p <= t; ++ p) {
            int buf1 = qpow(G, (mod - 1) / (1 << p));
            int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
            deer[0][p][0] = deer[1][p][0] = 1;
            for(int i = 1; i < (1 << p); ++ i) {
                deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//
                deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
            }
        }
        inv[1] = 1;
        for(int i = 2; i <= (1 << t); ++ i)
            inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }

    int NTT_init(int n) {//快速数论变换预处理
        int limit = 1, L = 0;
        while(limit < n) limit <<= 1, L ++ ;
        for(int i = 0; i < limit; ++ i)
            RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
        return limit;
    }

    void NTT(poly &A, int type, int limit) {//快速数论变换
        A.resize(limit);
        for(int i = 0; i < limit; ++ i)
            if(i < RR[i])
                swap(A[i], A[RR[i]]);
        for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
            int len = mid >> 1;
            for(int pos = 0; pos < limit; pos += mid) {
                int *wn = deer[type][j];
                for(int i = pos; i < pos + len; ++ i, ++ wn) {
                    int tmp = 1ll * (*wn) * A[i + len] % mod;
                    A[i + len] = ck(A[i] - tmp + mod);
                    A[i] = ck(A[i] + tmp);
                }
            }
        }
        if(type == 0) {
            for(int i = 0; i < limit; ++ i)
                A[i] = 1ll * A[i] * inv[limit] % mod;
        }
    }

    inline poly poly_mul(poly A, poly B) {//多项式乘法
        int deg = A.size() + B.size() - 1;

        int limit = NTT_init(deg);

        poly C(limit);
        NTT(A, 1, limit);
        NTT(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            C[i] = 1ll * A[i] * B[i] % mod;
        NTT(C, 0, limit);
        C.resize(deg);
        return C;
    }
    //多个多项式相乘CDQ或者利用优先队列启发式合并
    /*
    inline poly CDQ(int l,int r)
    {
      if(l==r)
      { 
        return poly{1,A[l]};
      }
      int mid=l+r>>1;
      poly L=CDQ(l,mid);
      poly R=CDQ(mid+1,r);
      return poly_mul(L,R);  
    }*/
}

using namespace Poly;
const int del=30000;
poly A(60005),B(120005),C(60005);
int main()
{
    //ios::sync_with_stdio(false);
    //cin.tie(nullptr);

    init(19);
    
    int n1,n2,n3;
    cin>>n1;
    for(int i=1;i<=n1;i++)
    {
        int x;
        cin>>x;
        A[x+del]=1;
    }
    cin>>n2;
    for(int i=1;i<=n2;i++)
    {
        int x;
        cin>>x;
        B[2*(x+del)]=1;
    }
    cin>>n3;
    for(int i=1;i<=n3;i++)
    {
        int x;
        cin>>x;
        C[x+del]=1;
    }
    

    poly D=poly_mul(A,C);

    int res=0;
   
    for(int i=0;i<120001;i++)
    if(D[i]!=0&&B[i]) res+=D[i];
    cout<<res<<'\n';
    
    return 0;

}
复制代码

 SEERC2020

B. Reverse Game

观察发现,这个reverse的形式类似于冒泡排序,于是考虑逆序对数

发现转一次10逆序对数110102

于是问题就变成了,一堆石子,最多一次取2个,问你先手后手赢

巴什博弈,结论是数量是3的倍数就后手必胜,否则先手。

证明的话考虑km是必败态,而先手必定可以构造一个这种状况(如果不是m的倍数)

虽然是队友切的

 

复制代码
#include<bits/stdc++.h>
using namespace std;
string s;
int main()
{
    cin>>s;
    int n=s.size();
    long long res=0,ans=0;
    for(int i=n-1; i>=0; i--)
    {
        if(s[i]=='0') res++;
        else ans=ans+res;
    }
    if(ans%3==0) cout<<"Bob"<<endl;
    else cout<<"Alice"<<endl;
    return 0;
}
复制代码
E. Divisible by 3

两两乘积和换成和的平方减去平方的和

然后统计一下前缀和就行。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
long long dp[5][5];
long long Sum,pSum,ans; 
int main(){
    scanf("%d",&N);
    dp[0][0]=1;
    for (int i=1;i<=N;i++){
        int x;
        scanf("%d",&x);
        Sum=(Sum+x)%3;
        pSum=(pSum+1ll*x*x)%3;
        for (int j=0;j<=2;j++)
            for (int k=0;k<=2;k++){
                int Now=(((Sum-j+3)%3*(Sum-j+3)%3)%3-(pSum-k+3)%3+3)%3;
                if (Now==0) 
                ans+=dp[j][k];
        }
        dp[Sum][pSum]++;
    }
    printf("%lld\n",ans);
}
复制代码

H. AND = OR

最开始给了个二分权值+树套树的做法,因为太麻烦了被队友叉出去了。

我的做法是,发现and单调减,or单调增

于是对于每次询问来说,二分那个答案的值

小于答案的全部做or,大于的全部做and

and 如果大于 or 就往右找,否则往左。

问题就变成维护区间中权值前/后缀的连续位运算和,离散化一下线段树套权值线段树应该能做。

队友的聪明做法是发现andor单增/减的性质是由于1的数量发生改变而产生的

于是直接去维护区间内每个数的1的个数的大小关系就好了。

代码还没仔细看,队友写的。

可能回头会去把自己的做法实现一下(咕咕咕

 

复制代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int MX=30;
const int tot_max=2e6+5;
struct Node {
    int val,num,i;
};
int n,q;
Node a[maxn];
int tot_or,tot_and,tree_or[tot_max],tree_and[tot_max],tree_cnt[tot_max],son_or[tot_max][2],son_and[tot_max][2];
int root_or[MX+2],root_and[MX+2],res_or,res_and,res_cnt;
bool cmp_node(const Node &a,const Node &b) {
    return a.num<b.num;
}
void tree_xg_or(int k,int last,int l,int r,int x,int z) {
    while (l<r) {
        tree_or[k]=tree_or[last]|z;
        tree_cnt[k]=tree_cnt[last]+1;
        int mid=(l+r)>>1;
        if (x<=mid) {
            son_or[k][1]=son_or[last][1];
            son_or[k][0]=++tot_or;
            k=tot_or, last=son_or[last][0], r=mid;
        } else {
            son_or[k][0]=son_or[last][0];
            son_or[k][1]=++tot_or;
            k=tot_or, last=son_or[last][1], l=mid+1;
        }
    }
    tree_or[k]=tree_or[last]|z;
    tree_cnt[k]=tree_cnt[last]+1;
}
void tree_xg_and(int k,int last,int l,int r,int x,int z) {
    while (l<r) {
        tree_and[k]=tree_and[last]&z;
        int mid=(l+r)>>1;
        if (x<=mid) {
            son_and[k][1]=son_and[last][1];
            son_and[k][0]=++tot_and;
            k=tot_and, last=son_and[last][0], r=mid;
        } else {
            son_and[k][0]=son_and[last][0];
            son_and[k][1]=++tot_and;
            k=tot_and, last=son_and[last][1], l=mid+1;
        }
    }
    tree_and[k]=tree_and[last]&z;
}
void tree_cx(int kOr,int lastOr,int kAnd,int l,int r,int x,int y) {
    if (x<=l && r<=y) {
        res_or|=tree_or[kOr];
        res_and&=tree_and[kAnd];
        res_cnt+=tree_cnt[kOr]-tree_cnt[lastOr];
        return;
    }
    int mid=(l+r)>>1;
    if (x<=mid) tree_cx(son_or[kOr][0],son_or[lastOr][0],son_and[kAnd][0],l,mid,x,y);
    if (mid<y) tree_cx(son_or[kOr][1],son_or[lastOr][1],son_and[kAnd][1],mid+1,r,x,y);
}
 
int sum_or[MX+5],sum_and[MX+5],cnt[MX+5];
int main() {
    scanf("%d %d",&n,&q);
    for(int i=1; i<=n; i++) {
        scanf("%d",&a[i].val);
        a[i].num=__builtin_popcount(a[i].val);
        a[i].i=i;
    }
    sort(a+1,a+1+n,cmp_node);
    int i=1;
    for(int w=0; w<=MX; w++) {
        if (w) root_or[w]=root_or[w-1];
        for(; i<=n && a[i].num==w; i++) {
            int last=root_or[w];
            tree_xg_or(root_or[w]=++tot_or,last,1,n,a[i].i,a[i].val);
        }
    }
    tree_and[0]=(1<<30)-1;
    i=n;
    for(int w=MX; w>=0; w--) {
        root_and[w]=root_and[w+1];
        for(; i && a[i].num==w; i--) {
            int last=root_and[w];
            tree_xg_and(root_and[w]=++tot_and,last,1,n,a[i].i,a[i].val);
        }
    }
    while (q--) {
        int l,r;
        scanf("%d %d",&l,&r);
        int sumCnt=0;
        for(int w=0; w<=MX; w++) {
            res_or=0, res_and=(1<<30)-1, res_cnt=0;
            tree_cx(root_or[w],(w==0 ?0 :root_or[w-1]),root_and[w],1,n,l,r);
            sum_or[w]=res_or;
            sum_and[w]=res_and;
            cnt[w]=res_cnt;
            sumCnt+=res_cnt;
        }
        bool ans=0;
        int numLess=0;
        for(int w=0; w<=MX; w++) {
            if (numLess && numLess<sumCnt && sum_or[w-1]==sum_and[w]) {
                ans=1;
                break;
            }
            if (sum_or[w]==sum_and[w] && cnt[w]>=2) {
                ans=1;
                break;
            }
            numLess+=cnt[w];
        }
        puts(ans ?"YES" :"NO");
    }
}
复制代码

 

L. Neo-Robin Hood

考虑一个native的想法。

考虑按照m从大到小排,p从小到大排。

那么,如果一个人能同时被偷/资助的话,那么肯定用一个双指针顺着取是最优的。

那么,我就只需要考虑出现冲突的情况。

考虑对于一个位置a,它同时需要被偷和被资助

那么我们假设

a,帮b

a,偷c两种情况。

前一种情况的贡献是mapb,后一种情况的贡献是mcpa

因为bc都是还没被取过的状态,且它们是当前最优

于是一定有pb<pc,mb<mc

那么不妨设取第一种情况

mapb>mcpa

再根据式子,则一定有mapb>mbpa

移项

则有ma+pa>mb+pb

满足这个条件的时候就偷,否则就帮

于是我们不妨按照mi+pi排序

在这种序列下,排序靠后的一定会相对排序靠前的优先被偷

于是我们枚举一个分段点,前半段找mi最大,后半段找pi最小即可。

随便说的,主要思路是队友提的,代码也是队友写的。

感觉自己这个证法很乱。

复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
     int m,p;
     bool operator < (const node &t) const
     {
         return m+p<t.m+t.p;
     }
};
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    vector<node>poli(n);
    for(int i=0;i<n;i++) cin>>poli[i].m;
    for(int i=0;i<n;i++) cin>>poli[i].p;
    sort(poli.begin(), poli.end());
    int l=0,r=n/2,res=0;
    auto check=[&](int k)->bool{
        if(k==0) return 1;
          priority_queue<int,vector<int>,greater<int>>M;
          priority_queue<int>P;
          vector<ll>sump(n),summ(n);
          for(int i=0;i<k;i++)
          {
               P.push(poli[i].p);
               sump[k-1]+=poli[i].p;
          }
          for(int i=k;i<n;i++)
          {
              if(P.top()<=poli[i].p) sump[i]=sump[i-1];
              else
              {
                  sump[i]=sump[i-1]+poli[i].p-P.top();
                  P.pop();
                  P.push(poli[i].p);
              }
          }
          for(int i=n-1;i>n-1-k;i--)
          {
              M.push(poli[i].m);
              summ[n-k]+=poli[i].m;
 
          }
          for(int i=n-1-k;i>=0;i--)
          {
              if(M.top()>=poli[i].m)
                  summ[i]=summ[i+1];
              else
              {
                  summ[i]=summ[i+1]+poli[i].m-M.top();
                  M.pop();
                  M.push(poli[i].m);
              
              }
          }
          for(int i=k;i<n-k+1;i++)
          {
              //cout<<summ[i]<<" "<<sump[i-1]<<'\n';
              if(summ[i]>=sump[i-1])return 1;
          }
          return 0;
    };
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid)) res=mid,l=mid+1;
        else r=mid-1;
    }
    cout<<res<<'\n';
    
    
 
}
复制代码

赛后补题:

F-Fence Job

感觉赛中真的被降智了……半天没弄出来。

考虑一个位置,往左右能延伸的最大距离。

考虑操作太复杂了,问题只要求统计答案。

其实问题则转化为,一个位置能把自己的颜色想左向右延伸到li,ri的位置

后填充的颜色覆盖先填充的颜色

问方案数。

dp[i][j]表示前i个数,最后一段填入的是aj

转移的时候,先考虑左半边

如果一个位置能延伸到i的话,那么它的方案数就是前面的位置能延伸到i位置的方案数的和(它替换掉最后一段)

那么,其实考虑把那些延伸不过来的位置也加进来(它们贡献是0)

其实就是一个前缀和,维护一下就好了。

然后右半边同理。

赛场上把问题转化出来了,dp想不出来,自闭了(

复制代码
#include <bits/stdc++.h>
using namespace std;
const int fish=1e9+7;
int dp[5005][5005];
int N,Sum[5005],h[5005],l[5005],r[5005];
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        scanf("%d",&h[i]);
    }
    for (int i=1;i<=N;i++){
        for (int j=i-1;;j--){
            if (h[i]>h[j]) {
                l[i]=j;
                break;
            }
        }
    }
    for (int i=1;i<=N;i++)
        for (int j=i+1;;j++){
            if (h[i]>h[j]) {
                r[i]=j;
                break;
            }
    }
    for (int i=1;i<=N;i++){
        if (l[i]<1) dp[1][i]++;
        (Sum[i]=Sum[i-1]+dp[1][i])%=fish;
    }
    for (int i=2;i<=N;i++){
        for (int j=1;j<=N;j++){
            if(l[j]<i&&i<r[j])
                (dp[i][j]+=Sum[j])%=fish;
            Sum[j]=(Sum[j-1]+dp[i][j])%fish;
        }
    }
    int ans=0;
    for (int i=1;i<=N;i++)
        (ans+=dp[N][i])%=fish;
    cout<<ans;
    return 0;
}
复制代码

 CRRC19


感觉做的挺顺的

A. Green tea


签到,没啥好说的

复制代码
#include <bits/stdc++.h>
using namespace std;
int T1,T2,ans1,ans2;
int minn=1e9+7;
int main(){
    minn=1e9+7;
    scanf("%d%d",&T1,&T2);
    for (int i=0;i<=1000;i++)
        for (int j=0;j<=1000;j++)
            if (T1*i+T2*j==80*(i+j)){
                if (i==j&&i==0) continue;
                minn=min(i+j,minn);
                if (i+j==minn) ans1=i,ans2=j;
            }
    printf("%d %d",ans1,ans2);
    return 0;
} 
复制代码

B. Mysterious Resistors

高中物理

发现随着R增大,总电阻增大

二分一下就好了

复制代码
#include <bits/stdc++.h>
using namespace std;
const double eps=1e-6;
double R;
int N;
double rr[5005];
int Check(double nw){
    double anss=0;
    for (int i=1;i<=N;i++){
        double nww=(rr[i]*nw)/(rr[i]+nw);
        anss+=nww;
    }
    //cout<<nw<<" "<<anss<<endl;
    if (fabs(anss-R)<=eps) return 2;
    if (anss>R) return 1;
    else return 0;
}
int main(){
    scanf("%d%lf",&N,&R);
    double Sum=0;
    for (int i=1;i<=N;i++){
        scanf("%lf",&rr[i]);
        Sum+=rr[i];
    }
    double l=0,r=Sum,mid;
    while (l<r){
        mid=(l+r)/2;        
        //printf("%lf %lf %lf\n",l,r,mid);
        if (Check(mid)==2){
            printf("%.10lf\n",mid);
            return 0;
        }
        if (Check(mid)==1) r=mid;
        else l=mid;
    }
    return 0;
}
复制代码

C. Emoticons

学长写的,听说是模拟

复制代码
#include <bits/stdc++.h>
using namespace std;
string emo[]={"[:|||:]" ,":-\\" ,":-P", ":D" ,":C", "8-0",":-|","%0",":-0" ,":-E",":-X" , ":~(" ,
                  ";-)" , ";-(" , ":)" , ":(" };
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string s;
    cin>>s;

    int n=s.size();
    vector<int>vis(n+1),pos;
    for(int i=0;i<16;i++)
    {
        
        
        while(true)
        {
            bool ok=1;
            pos.clear();
            for(int j=0;j<emo[i].size();j++)
            {
                bool flag=0;
                for(int k=0;k<n;k++)
                {
                    if(s[k]==emo[i][j]&&!vis[k])
                    {
                        vis[k]=1;
                        flag=1;
                        pos.push_back(k);
                        break;
                    }
                }
                ok&=flag;
                if(!ok)
                {
                    for(auto x:pos) vis[x]=0;
                    break;
                }
            }
            if(!ok)break;
            else if(ok) cout<<emo[i]<<'\n';
        }
        
    }
    cout<<"LOL\n";
    return 0;
}
 
复制代码

D. Power play

构造一个函数,求个导,算个极值点,然后以极值点为分界左右二分找零点

代码是学长写的

复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const double eps=1e-9;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    ll a,b;
    cin>>a>>b;
    auto f=[&](auto self,ll l,ll r)->ll{
        if(l>r) return 0;
        ll mid=l+r>>1;
        double x=1.0*mid*log(a)-1.0*b*log(mid);
        if(abs(x)<eps)
        {
            ll ans=0;
            ans=self(self,1,mid-1);
            if(ans) return ans;
            return mid;
        }
        if(1.0*mid*log(a)>1.0*b*log(mid))
            return self(self,l,mid-1);
        return self(self,mid+1,r);
    };
    cout<<f(f,1,1e18)<<'\n';
    return 0;

}
复制代码

F. A word game

每个字符都是独立的,问题就等价于有N堆石子,每次取1,2个或者全部

问最终谁赢

算算SG函数就行了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int a[55];
int SG[55];
int GetSG(int x){
    int mex[55];
    memset(mex,0,sizeof(mex));
    if (SG[x]!=-1) return SG[x];
    if (x>=2) mex[GetSG(x-2)]=1;
    if (x>=1) mex[GetSG(x-1)]=1;
    mex[GetSG(0)]=1;
    for (int i=0;;i++)
        if (mex[i]==0) {
            SG[x]=i;
            return SG[x];
    }
}
int main(){
    string ss;
    cin>>ss;
    int Len=ss.length();
    for (int i=0;i<Len;i++){
        a[ss[i]-'A']++;
    }
    memset(SG,-1,sizeof(SG));
    SG[0]=0;
    int ans=0;
    for (int i=0;i<26;i++){
        ans^=GetSG(a[i]);
        //cout<<a[i]<<" "<<GetSG(a[i])<<endl;
    }
    //cout<<SG[2]<<" "<<SG[1]<<endl;
    if (ans==0) printf("Bob\n");
    else printf("Alice\n");
    return 0;
}
复制代码

H. Men's showdown

学长弄的,听说也是个博弈

复制代码
#include <bits/stdc++.h>
using namespace std;
int dp[10005];
int op[]={1,5,13};
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    dp[0]=1;
    for(int i=1;i<=10000;i++)
    {
        dp[i]=1;
        int ok=1;
        for(int j=0;j<3;j++)
            if(i>=op[j]) ok&=dp[i-op[j]];
        if(ok) dp[i]=0;
    }
    int n;
    cin>>n;
    if(dp[n]) cout<<1<<'\n';
    else cout<<2<<'\n';
}
复制代码

I. Andrew and Python

交互题。

我的想法是先把正方形分成四块三角形

然后每块三角形再每次切两半

这样的话,面积每次至少缩小一半

log级别的次数内可以让三角形的面积趋于0,算一下大概40多次就行了。

代码是学长写的。

复制代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<ll, ll> pll;
inline ll read() {
    ll s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
ll const maxn = 2e5 + 10;
ll n, m, T;
pll get_mid(pll x, pll y, pll z) {
    double resx = (1.0 * x.first + 1.0 * y.first) / 2.0;
    double resy = (1.0 * x.second + 1.0 * y.second) / 2.0;
    if (z.first < resx)
        resx = ceil(resx);
    else
        resx = floor(resx);
    if (z.second < resy)
        resy = ceil(resy);
    else
        resy = floor(resy);
    return {(ll)resx, (ll)resy};
}
ll test(pll a, pll b, pll c) {
    if ((c.second - b.second) * (b.first - a.first) ==
            (b.second - a.second) * (c.first - b.first))
        return 1;
    else
        return 0;
}
ll check(pll a, pll b, pll c) {
    if (!test(a, b, c)) {
        cout << "? 3 " << a.first << " " << a.second << " " << b.first << " "
             << b.second << " " << c.first << " " << c.second << endl;
        fflush(stdout);
        string s;
        cin >> s;
        if (s == "Yes")
            return 1;
        else
            return 0;
    } else {
        if (a == b) {
            return 0;
        } else {
            cout << "? 2 " << a.first << " " << a.second << " " << b.first
                 << " " << b.second << endl;
            fflush(stdout);
            string s;
            cin >> s;
            if (s == "Yes")
                return 1;
            else
                return 0;
        }
    }
}
ll check2(pll a, pll b, pll c) {
    ll ab = (a.first - b.first) * (a.first - b.first) +
            (a.second - b.second) * (a.second - b.second);
    ll bc = (b.first - c.first) * (b.first - c.first) +
            (b.second - c.second) * (b.second - c.second);
    ll ac = (a.first - c.first) * (a.first - c.first) +
            (a.second - c.second) * (a.second - c.second);
    if (ab < 4 && bc < 4 && ac < 4) return 0;
    return 1;
}
ll Query(pll x) {
    cout << "? 1 " << x.first << " " << x.second << endl;
    fflush(stdout);
    string s;
    cin >> s;
    if (s == "Yes")
        return 1;
    else
        return 0;
}
void print(pll x) {
    cout << "! " << x.first << " " << x.second << endl;
    fflush(stdout);
}
int main() {
    cin >> n;
    pll a, b, c;
    c = {1, 1}, a = {1, n}, b = {n, 1};
    if (!check(a, b, c)) c = {n, n};
    while (check2(a, b, c)) {
        pll mid = get_mid(a, b, c);
        if (check(a, c, mid)) {
            b = c, c = mid;
        } else {
            a = b, b = c, c = mid;
        }
    }
    if (Query(a)) {
        print(a);
    } else if (Query(b)) {
        print(b);
    } else if (Query(c)) {
        print(c);
    }
    return 0;
}
复制代码

J. Something that resembles Waring's problem

搞出这题我能吹一年好吧(

学长开始看的时候,说考虑能不能是连续的自然数

于是我就设了一个中间值t

然后t+1,t1,t三个数的三次方加起来

发现t好像有点多余

t1t+1的立方和是6t+t3

于是考虑再拿两个t3,把数字变成6t,看起来干净一点

然后就变成拆分出6t+m3=N,发现只要m3N同余就好了。

代码是学长写的,py大数

n = input()
n = int(n)
r = n % 6
m = r * r * r
t=(n-m)/6
print('5')
print('%d %d %d %d %d' %(r, ( t + 1), (t - 1), (-t), (-t)))

K. Parabolic sorting

总感觉是哪年的FJOI题……

从大到小考虑,把最大数往左/右移

然后考虑次大数

如果最大值在它左边的话,那么它这次往左的代价就是pos1,否则是pos

于是合理推广一下,对于一个位置,我们找出它左边有几个比它大的,用位置减去这些比它大的数字的个数,就是这次它往左的贡献

发现其实这个贡献是比它小的数字的个数,

离散化树状数组维护一下结束(

复制代码
#include<bits/stdc++.h>
using namespace std;
int Tree[210005],N;
const int mx=200005;
int a[100005],b[100005],L[100005],R[100005];
map<int,int> lst;
int Lowbit(int x){ return (x&(-x));}
int query(int x){int ans=0; for (int i=x;i;i-=Lowbit(i)) ans+=Tree[i];return ans;}
void Insert(int x,int v){for (int i=x;i<=mx;i+=Lowbit(i)) Tree[i]+=v;}
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(a+1,a+N+1);
    for (int i=1;i<=N;i++){
        int x=b[i];
        if (lst[b[i]]) b[i]=lst[b[i]]+1;else b[i]=lower_bound(a+1,a+N+1,b[i])-a;
        lst[x]=b[i];
    }
    for (int i=1;i<=N;i++){
        L[i]=query(b[i]-1);
        Insert(b[i],1);
    }
    memset(Tree,0,sizeof(Tree));
    for (int i=N;i>=1;i--){
        R[i]=query(b[i]-1);
        Insert(b[i],1);
    }
    int ans=0;
    for (int i=1;i<=N;i++){
        ans+=min(L[i],R[i]);
    }
    cout<<ans;
    return 0;
}
复制代码

 

posted @   si_nian  阅读(93)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
欢迎阅读『xcpc区域赛训练记录』
点击右上角即可分享
微信分享提示