CSP-S模拟15 网格图 保险箱 追逐 字符串

T1【暴力枚举+线性转移】给你nn的网格,“.”代表可以联通,“X”代表不可以,上下左右4个方向如果都联通就可以连边,给出K,求如果可以覆盖KK的网格使得全部都是“.”那么最多可以产生的最大联通块的大小。(n<=500)

考场

想到线性递推但是不会维护联通块,打暴力,但是对于四个角在1*1的网格会算4次没办法处理,直接subtask爆0.

正解

发现每次对于选择的格子移动一个块,只会有2列的格子变化,所以就想到每次移动维护2个部分
(1)KK的矩阵内部的联通点,提前在处理好的联通块中siz删除,答案直接累加KK,在转移的时候删除少的一条边(siz++),添加多的(siz--)
(2)对于外部能造成的贡献只来自与4个边际线,所以每次(x,y)-->(x,y+1)暴力4*n跑4个边,把联通块打上vis标记存到数组里,代表要累加的。

点击查看代码


#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=510;
char s[N];
int f[N][N],id[N][N],fa[N*N],siz[N*N],vis[N*N],has[N*N],net;
int n,K,tot;
inline int getfa(int x)
{
    if(x==fa[x])return x;
    return fa[x]=getfa(fa[x]);
}
inline void merge(int x,int y)
{
    x=getfa(x),y=getfa(y);
    if(x==y)return;
    fa[x]=y;siz[y]+=siz[x];
}
int main()
{
 // freopen("grid2.in","r",stdin);
  // freopen("1.out","w",stdout);
    n=re(),K=re();
    _f(i,1,n)
    {
        scanf("%s",s+1);
        _f(j,1,n)
        {
            if(s[j]=='.')
            {
                f[i][j]=1;
                id[i][j]=++tot;
                siz[tot]=1;
                fa[tot]=tot;
            }
        }
    }
    _f(i,1,n)
    _f(j,1,n)
    {
        if(f[i][j]&&f[i-1][j])merge(id[i][j],id[i-1][j]);
        if(f[i][j]&&f[i][j-1])merge(id[i][j],id[i][j-1]);
    }
    int  ans=0;
    // _f(i,1,n)
    // _f(j,1,n)
    // if(f[i][j])
    // {
    //     chu("%d %d:siz:%d\n",i,j,siz[getfa(id[i][j])]);
    // }
    _f(x1,1,n-K+1)
    {
        int x2=x1+K-1;
        int sum=0;
        net=0;
        _f(i,x1,x2)
        _f(j,1,K)
        {
            if(f[i][j])
            {
                int fat=getfa(id[i][j]);
                siz[fat]--;
            }
        }
        _f(y1,1,n-K+1)
        {
            int y2=y1+K-1;
            //(x1,y1)
            sum=K*K;net=0;
            _f(i,x1,x2)
            {
                if(f[i][y1-1])
                {
                    int fat=getfa(id[i][y1-1]);
                    if(!vis[fat])
                    {
                        has[++net]=fat;
                        vis[fat]=1;
                    }
                }
                if(f[i][y2+1])
                {
                    int fat=getfa(id[i][y2+1]);
                    if(!vis[fat])
                    {
                        has[++net]=fat;
                        vis[fat]=1;
                    }
                }
            }
            _f(i,y1,y2)
            {
                if(f[x1-1][i])
                {
                    int fat=getfa(id[x1-1][i]);
                    if(!vis[fat])
                    {
                        has[++net]=fat;
                        vis[fat]=1;
                    }
                }
                if(f[x2+1][i])
                {
                    int fat=getfa(id[x2+1][i]);
                    if(!vis[fat])
                    {
                        has[++net]=fat;
                        vis[fat]=1;
                    }
                }
            }
            _f(i,1,net)
            {
                vis[has[i]]=0;
                sum+=siz[has[i]];
            }
            if(y2==n)
            {
                ans=max(ans,sum);
                _f(i,x1,x2)
                _f(j,y1,y2)
                {
                    if(f[i][j])
                    {
                        int fat=getfa(id[i][j]);
                        siz[fat]++;
                    }
                }
            }
            else
            {
                ans=max(ans,sum);
                _f(i,x1,x2)
                {
                    if(f[i][y1])
                    {
                        int fat=getfa(id[i][y1]);
                        siz[fat]++;
                    }
                    if(f[i][y2+1])
                    {
                        int fat=getfa(id[i][y2+1]);
                        siz[fat]--;
                    }
                }
            }
        }
    }
    chu("%d",ans);
    return 0;
}
/*
5 2
..XXX
XX.XX
X.XXX
X...X
XXXX.

*/

T2【数论:裴蜀定理+群论】定义如果(x,y)属于群,那么(x+y)%mod也属于群,给出mod和K个数,其中K-1个数不属于这个群,第K个属于这个群,求这个群最多包含的元素个数。(n<=1e14,K<=2.5e5)

考场

去枚举了gcd的因数,然后从K-1个数里依次判断,T了。

正解

假设这个群为S
结论【1】:如果\(x\epsilon S\)那么gcd(x,n)也属于S。ax-bn=gcd(a,b)一定有解
结论【2】:如果(x,y)属于S,那么gcd(x,y)属于S。
只要构造一组mx+ny同余gcd(x,y)(%mod)
\(ax+by\equiv gcd(a,b),ax+by+mod*m*x+mod*n*y\equiv gcd(a,b)\)
\((a+m*mod)*x+(b+n*mod)*y\equiv gcd(a,b)\)
因为x,y的系数都是正数,所以左边一定属于S,所以右边属于S。
对于给出的mod和aK,取出gcd U,那么假设这个群里的元素的gcd是u,u一定是gcd的因数,但是u不能是a1 ~ k-1的因数,否则可以被凑出。
考虑对U进行因数的选择,从小到大筛出因数,考虑对于这K-1个数,如果能被某个因数整除,那么这个因数一定是gcd(aj,U),所以值拿出gcd就行。
在决策答案有2个剪枝
(1)把aj按照从大到小排序,如果aj已经<ans_now,一定不会是倍数了。
(2)去重。
\(O(因数^2)\)

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=510;
ll n,K;
ll M[250000+100],ys[10000000+100],cnt;int f[10000000+100];
inline ll gcd(ll a,ll b)
{
    if(!b)return a;
    return gcd(b,a%b);
}
int main()
{
  // freopen("b3.in","r",stdin);
  // freopen("1.out","w",stdout);
    n=re(),K=re();
    _f(i,1,K)M[i]=re();
  //  chu("df\n");
    ll S=gcd(n,M[K]);
    _f(i,1,K-1)
    M[i]=gcd(M[i],S);
    sort(M+1,M+1+K-1);
    K=unique(M+1,M+1+(K-1))-M-1;
    //chu("df\n");
    for(ll i=1;i*i<=S;++i)
    {
        if(S%i==0)
        {
            ys[++cnt]=i;
            //f[cnt]=1;
            if(i*i!=S)ys[++cnt]=S/i;
           // f[cnt]=1;
        }
    }
    sort(ys+1,ys+1+cnt);
    reverse(M+1,M+1+K);
    _f(i,1,cnt)
    {
        int yes=1;
        _f(j,1,K)
        {
            if(M[j]%ys[i]==0){yes=0;break;}
            if(M[j]<ys[i])break;
        }
        if(yes)
        {
            chu("%lld",n/ys[i]);return 0;
        }
    }
    return 0;
}
/*
42 5
28 31 10 38 24

*/

T3【树上DP:换根DP+问题转化】给出n个节点的无根树,如果某个人不重复边地从起点走到终点,它的val是经过节点的所有权值和,它的对手会走完全重复的它走过的路,val也是经过的权值和,给出这个人有k次机会,经过某个节点可以把所有相邻节点的权值都转移到这个节点上,它自己只计算自己走到这里的权值。求对手val-它自己的val的最大值。(n<=1e5)

考场

\(O(2^K*2^n*n)\)的暴力,写挂了。
因为没考虑(1)经过节点必须头尾都是叶子(2)不能走分叉,所以会多计算很多不合法状态。但是写对有20分。

正解

直接考虑对差值计算贡献。一个节点如果使用机会,贡献是它的儿子的权值和。(有根)。对于不用机会,cha=0,使用机会,父亲val跑到x没用,因为无论如何都会2个人都算上,儿子跑到val,对于先手来说不会计算,以后也没机会了(不能重复走),对于后手却会收集到所有留下的val。
钦定一个节点是根,然后然后换根DP
x的答案(1)根-->x->son(2)son-->x-->根(3)son1-->x-->son2(4)x-->root(5)x-->son
方向会影响答案
设置
g[x][y][0/1]表示从x子树走到x,用了y次机会,x用/不用机会的最大价值
f[x][y][0/1]表示x走到x子树。
区别在于计算贡献
我先把子树的g和f计算出来,为了保证路径不重复,在x上合并,顺次合并。
我只考虑(3)(4)(5),也就是x作为“中转点”的答案。
already_son-->x-->now_son:因为f默认向上走root,所以计算进去了now_son的贡献没有算fa,所以要倒一下。
对于转移前缀max优化。
// f[x][y][0/1]表示从x到子树选了y个点的最大价值(不选/选)
// g[x][y][0/1]表示从子树到x选了y个点的最大价值(不选/选)
// 转移:
// f[u][j][0]=max(f[son][j][0],f[son][j][1])
// f[u][j][1]=max(f[son][j-1][0],f[son][j-1][1])+val[u]
// g[u][j][0]=max(g[son][j][0],g[son][j][1])
// g[u][j][1]=max(g[son][j-1][0],g[son][j-1][1])+val[u]+p[fa]-p[son]
// 决策答案:
// ans=max
// (1)max(g[u][j][0/1])+max(f[son][k][0/1])
// (2)max(f[u][j][0/1]+p[fa]-p[son])+max(g[son][k][0/1])
// 边界:
// f[u][0][1]=-inf
// g[u][0][1]=-inf
// g[u][1][1]=val[u]+p[fa]

点击查看代码



#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=1e5+100;
int nxt[N<<1],ver[N<<1],head[N],p[N],n,V,tot;
ll val[N],f[N][105][2],g[N][105][2],ans;
inline void Add(int x,int y)
{
    ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;
}

inline void Dfs(int x,int fa)
{
    for(rint i=head[x];i;i=nxt[i])
    {
        int to=ver[i];
        if(to==fa)continue;
        Dfs(to,x);
        val[x]+=p[to];
    }
   // chu("val[%d]:%lld\n",x,val[x]);
    g[x][0][1]=-1e18,f[x][0][1]=-1e18;//但是选择更多不是更不合法吗?不用都清掉?
    g[x][1][1]=val[x]+p[fa];
    for(rint i=head[x];i;i=nxt[i])
    {
        ll mx1=0,mx2=0;int son=ver[i];
        //更新答案
        f_(j,V,0)
        {
            mx1=max(g[x][V-j][0],max(mx1,g[x][V-j][1]));
            mx2=max(f[x][V-j][1]+p[fa]-p[son],f[x][V-j][0]);
            ans=max(ans,max(f[son][j][0],f[son][j][1])+mx1);
            ans=max(ans,max(g[son][j][0],g[son][j][1])+mx2);
        }
        _f(j,1,V)//枚举选多少个,一个都不选不用枚举,就是0
        {
            f[x][j][0]=max(f[x][j][0],max(f[son][j][0],f[son][j][1]));
            f[x][j][1]=max(f[x][j][1],max(f[son][j-1][0],f[son][j-1][1])+val[x]);
            g[x][j][0]=max(g[x][j][0],max(g[son][j][0],g[son][j][1]));
            g[x][j][1]=max(g[x][j][1],max(g[son][j-1][0],g[son][j-1][1])+val[x]+p[fa]-p[son]);
        }
    }
    //chu("nowans:%lld\n",ans);
}
int main()
{
  //freopen("c3.in","r",stdin);
  // freopen("1.out","w",stdout);
    n=re();V=re();
    _f(i,1,n)p[i]=re();
    _f(i,1,n-1)
    {
        int u=re(),v=re();Add(u,v);Add(v,u);
    }
    Dfs(1,0);
    chu("%lld",ans);
    return 0;
}



/*
12 2
2 3 3 8 1 5 6 7 8 3 5 4
2 1
2 7
3 4
4 7
7 6
5 6
6 8
6 9
7 10
10 11
10 12
*/

T4【字符串问题转化+线段树维护前缀后缀和】给出一个字符串S,和Q个询问(L,R)。求把[L,R]区间中的字符串删除一部分使得任意前缀后缀cnt_C>cnt_T(字符串只有CT)\(n<=5e5\)

对C赋值1,T-1,对子串求前缀后缀和,发现就是删除-1、-2、-3、-4...的位置。但是后缀要求是已经删除之后的,所以通过前缀求出后缀信息。
\(pre^{min}是前缀答案。pre^p,截止p位置,后缀q的答案:suf^q=suf^q-pre^{min}+prep(p>q),ans=pre^min+suf^{qmin}\)
代入发现\(ans=pre^{pmin}+suf^{qmin}\)线段树维护。
为什么前缀p之后的贡献一定减去到了后缀q之后:p就是保证q前最小。

点击查看代码


#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
    ll x=0,h=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')h=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*h;
}
const int N=5e5+100;
struct Seg
{
    int lmx,rmx,sum,ans;
}t[N<<2];
int n,F[N],q;
char s[N];
inline Seg operator + (const Seg A,const Seg B)
{
    return {max(A.lmx,A.sum+B.lmx),max(B.rmx,B.sum+A.rmx),A.sum+B.sum,max({A.ans+B.sum,B.ans+A.sum,A.lmx+B.rmx})};
}
#define lson (rt<<1)
#define rson (rt<<1|1)
inline void Build(int rt,int l,int r)
{
    if(l==r)
    {
        t[rt]={max(0,F[l]),max(0,F[l]),F[l],max(0,F[l])};
        return;
    }
    int mid=(l+r)>>1;
    Build(lson,l,mid);Build(rson,mid+1,r);
    t[rt]=t[lson]+t[rson];
}
inline Seg Query(int rt,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)return t[rt];
    int mid=(l+r)>>1;
    if(R<=mid)return Query(lson,l,mid,L,R);
    else if(L>mid)return Query(rson,mid+1,r,L,R);
    else return Query(lson,l,mid,L,R)+Query(rson,mid+1,r,L,R);
}
int main()
{
  // freopen("1.in","r",stdin);
  // freopen("1.out","w",stdout);
    n=re();
    scanf("%s",s+1);
    _f(i,1,n)if(s[i]=='T')F[i]=1;else F[i]=-1;
    Build(1,1,n);
    q=re();
    _f(i,1,q)
    {
        int l=re(),r=re();
        chu("%d\n",Query(1,1,n,l,r).ans);
    }
    return 0;
}
/*
11
CCCTTTTTTCC
3
1 11
4 9
1 6
*/
posted on 2022-10-02 06:35  HZOI-曹蓉  阅读(33)  评论(0)    收藏  举报