2019暑假记录下


 7.29 Day1 zjo学长的网络流
  T1 (game) 题意:Alice和Bob玩游戏,Alice先手。每一轮可以将一个数变为它的一个不是它自身和1的约数。无法行动者获胜。给出数n,问谁会赢。T组数据。\(T\leq 100,n\leq 10^{12}\)
  题解:分解质因数(因数也行),若正好有两个则Bob胜,否则Alice胜。线性筛出\(10^{6}\)的质数,剩余大于\(10^{6}\)的数时特判一下。
  复杂度\(O(\sqrt n)\)

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
long long prime[maxn];
int vis[maxn],cnt_prime,num;
inline void eular(int x)
{
    for(int i=2;i<=x;++i)
    {
        if(!vis[i]) prime[++cnt_prime]=i;
        for(int j=1;j<=cnt_prime&&i*prime[j]<=x;++j)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}
inline bool factor(long long x)
{
    num=0;
    for(int i=1;i<=cnt_prime&&prime[i]<=x;++i)
    {
        if(x%prime[i]) continue;
        while(x%prime[i]==0)
        {
            x/=prime[i];
            ++num;
        }
    }
    if(x>1000000) ++num;
    if(num==2) return 0;
    return 1;
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    int T;
    long long a;
    scanf("%d",&T);
    eular(1000000);
    while(T--)
    {
        scanf("%lld",&a);
        if(factor(a)) puts("Alice");
        else puts("Bob");
    }
    return 0;
}

 T2 (kun) 题面:有n条鲲(手动滑稽),每只鲲有一个流量值a。m个鬼畜视频,每个视频可以消灭一只流量值小于等于d的鲲,有c的费用。求出消灭所有鲲的最小费用。若无法消除所有则输出“JiNiTaiMei!”(英文标点,不带引号)。所有数据\(\leq 10^{6}\)
  题解:桶排,相同值用链表存,每次找到离这个视频比它小的最近的鲲消灭掉。
  复杂度\(O(n\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
#define maxnn 1000005
int head[maxnn],fr[maxnn],fa[maxnn],t[maxnn],d[maxnn],c[maxnn];
long long ans;
inline int findf(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findf(fa[x]);
}
inline void add(int st,int ed)
{
    fr[ed]=head[st];
    head[st]=ed;
}

int main()
{
    freopen("kun.in","r",stdin);
    freopen("kun.out","w",stdout);
    int n,m,maxn=0,maxm=0,kun;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&kun),maxn=max(maxn,kun),++t[kun];
    for(int i=1;i<=maxn;++i)
    {
        if(t[i]) fa[i]=i;
        else fa[i]=findf(i-1);
    }
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&d[i],&c[i]);
        maxm=max(maxm,c[i]);
        if(d[i]>maxn) d[i]=maxn;
        add(c[i],i);
    }
    for(int i=0;i<=maxm;++i)
        if(head[i])
            for(int j=head[i];j;j=fr[j])
            {
                int tmp=findf(d[j]);
                if(!tmp) continue;
                --n,ans+=i;
                if(!--t[tmp]) fa[tmp]=findf(tmp-1);
                if(!n)
                {
                    printf("%lld",ans);
                    return 0;
                }
            }
    puts("JiNiTaiMei!");
    return 0;
}

 T3 (canel) 题意:给定\((n+1)*(m+1)\)的网格图,只能向右或向下走。第一行和最后一行分别有p个点。图中有q个点不能经过。求总方案数%998244353 \(n,m\leq 100000\) \(p,q\leq 200\)
  题解:直接蒯学长的题解吧。
  下面我们讨论在给定一对红蓝点之间有多少条线路.
  若不存在障碍点,一对点直接的路径条数显然是C(Δx+Δy,Δx),Δx、Δy分别为第一、二维坐标的差值.
  若存在障碍点,整张图是一个DAG,所以可以把中间的障碍点和蓝点进行拓扑排序.记F[i]表示从红点到第i个关键点,中间不经过障碍点的方案数,考虑容斥原理,f[i]=总方案数减去Σf[j]*j到i的方案数,j为拓扑序在i之前的点。
  对每对红蓝点间均应用上述过程,即得一个\(O(pq^{2})\)的算法解决不考虑交叉的问题。
  然后考虑路径交叉的情况,先预处理出way[i][j]表示第i个红点到第j个蓝点的方案数(先不考虑相交的情况)。这个可以\(O(pq^{2}+q^{2}q)\)的预处理出来。
  再次使用容斥原理,考虑每个红点与蓝点配对,总共有\(n!\)种方案.只有一种方案是合法方案(即按次序配对).但是这一种方案会出现相交路径,所以要减去有相交的路径。
  除了这一种方案之外的其他方案是必然有相交路径的,按照容斥原理,必然有两条路径相交的要减去,必然有三条路径相交的要加,以此类推。
  i按升序取1到p,对应顺序写下j序列,可以发现,若j序列的逆序对数为偶数,则加,否则则减。
  这个容斥的式子恰好与行列式的定义相吻合,所以只要把way数组看成行列式,然后求出这个行列式的值即可.
行列式的值的求法:根据行列式的性质,把行列式的任意一行(列)的元素乘以同一个数后,加到另一行(列)的互对应元素上去,行列式的值不变。然后就可以像高斯消元一样消成一个上三角,主对角线乘积就是答案。
总复杂度:\(O(p^{3}+pq^{2}+p^{2}q)\)
代码?又咕了


 7.30 Day2 数学与概率期望
  T1(buff) 和7.23 T3 一模一样 略
  T2(pkd)(话说不是pdk吗?)
  题意:就是打跑得快只不过炸弹不算出牌次数
  给定一副牌,问最小出完牌的次数。多组数据。\(T\leq 1000,n\leq 23\)
  题解:搜索+dp。
  设\(f[i][j][k][l]\)表示能打4张的还有i种,3张的还有j种,以此类推。顺子直接爆搜。注意拆牌也可以转移。总共好像有十几种转移来着
  复杂度\(O(能过)\)
代码?又咕了
 T3 (could)话说不应该是cloud吗
  题意:给定c,w。每个光顾商店的人有一个氪金值\(a_{i}\)和一个欧气值\(b_{i}\) ,若他的欧气值大于等于c,他会选择连抽c次获得卡片,若他的欧气值小于c,且他的氪金值大于等于卡片的价格p,他会氪金买他想要的卡片.否则他会离开商店.
  现在,游戏公司要做的就是定下需要连抽多少次才能保证获得想要的卡片,即一个人连抽c次游戏公司可以赚取\(c*w\)的利润。游戏公司想要你帮他计算c定为\(1\sim max(c_{i})+1\)时的最大利润。(p不确定)。\(n,a_{i},b_{i}\leq 100000\),\(w\leq 10000\)
  题解:又是蒯的
  按照\(b_{i}\)从小到大排序,依次枚举,i及以后所有人都会选择抽卡.
  现在要考虑的就是i以前的人,你需要确定一个p使得利润最大.
  设f[j]表示把p设为j,当前的最大利润,那么每次加入一个买卡的人,就是j属于\(1\sim a_{i}\) 的每个\(f[j]+=j\).
  所以题目转化为:写一个数据结构,支持以下几种操作:
  1.区间内每个点加上i
  2.询问区间最大值.
  传统的数据结构无法胜任,考虑分块.
  完整的块打标记,不完整的块直接暴力加.
  对于每一块,答案就是\(tag*i+a_{i}\)的最大值,把tag看成x,i看成k,因为k单调,tag每次+1,所以可以使用斜率优化来维护这一块的最优解.
  对于不完整块的修改,暴力重构即可.
  复杂度:\(O(n\sqrt n)\)
代码又咕了


7.31 Day 1 在vjudge上评测 全都是Codechef的题
  A Xenny and Alternating Tasks
  题意:有两个人,n件事,两个人做每件事都有不同的时间。如果第一个人做第一件事,则奇数次都是他做,反之依然。\(n\leq 20000\)
  题解:纯模拟题。
  复杂度\(O(n)\)

#include<cstdio>
using namespace std;
int a[20005],b[20005];
inline int min(int x,int y)
{
    return x<y?x:y;
}
int main()
{
    int n,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;++i)
            scanf("%d",&b[i]);
        int ans1=0,ans2=0;
        for(int i=1;i<=n;++i)
            ans1+=(i&1)?a[i]:b[i];
        for(int i=1;i<=n;++i)
            ans2+=(i&1)?b[i]:a[i];
        ans1=min(ans1,ans2);
        printf("%d\n",ans1);
    }
    return 0;
}

 B Bear and Extra Number
  题意:有一段连续序列,每个数只出现了一次。现在加入了一个数,求出加入的数。\(n\leq 100000\)
  题解:直接排序扫一遍。
  复杂度\(O(n\log n)\)不过好像也能做到\(O(n)\)

#include<cstdio>
#include<algorithm>
using namespace std;
int a[100005],bucket[100005];
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        if(a[1]+1!=a[2])
        {
            printf("%d\n",a[1]);
            continue;
        }
        if(a[n-1]+1!=a[n])
        {
            printf("%d\n",a[n]);
            continue;
        }
        for(int i=1;i<=n;++i)
            if(a[i]==a[i-1])
            {
                printf("%d\n",a[i]);
                break;
            }
    }
    return 0;
}

 C Cooking Schedule
  题意:有一个长度为n的01序列,将其中至多k个位置进行反转,使序列中连续0或连续1的长度最小,并求出这个长度。\(n\leq 10^{6}\)
  题解:二分。预处理出每一段连续串的长度。对每一段序列,如果它的长度要小于等于x,则可以在其中反转\(\frac {len_{i}}{x+1}\)个使其符合要求。如果反转总数\(\leq k\)则符合要求。注意x=1时需要特判一下。
  复杂度\(O(n+\log n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
char s[1000005];
int num[1000005],cnt,cnt2,n,k;
inline bool chk()
{
    int ans=0;
    for(int i=1;i<=n;++i)
        if(s[i]-'0'==(i&1)) ++ans;
    return ans<=k||(n-ans)<=k;
}
inline bool check(int limit)
{
    int ans=0;
    for(int i=1;i<=cnt;++i)
        ans+=num[i]/(limit+1);
    return ans<=k;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        scanf("%s",s+1);
        if(chk())
        {
            puts("1");
            continue;
        }
        cnt=num[1]=1;
        for(int i=1;i<=n;++i)
        {
            if(s[i]==s[i-1]) ++num[cnt];
            else num[++cnt]=1;
        }
        int l=2,r=n,ans;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid)) r=mid-1,ans=mid;
            else l=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

 D Subtree Removal
  给定n个节点的有根树(节点编号为 \(1\sim n\)),根节点为 1号节点。每个节点都有点权,记第i个节点的点权为Ai。\(n\leq 10^{5}\)
  你可以任意次(包括零次)进行下面的操作:选择树中的某个节点,并删去包括该节点在内
的整棵子树。
  记收益为树中剩下的节点的点权之和减去 X × k,其中 k 代表操作次数。求最大收益。
  题解:树形dp。设\(dp[i]\)表示以第i节点为根节点的最大收益,\(wei[i]\)为i子树点权和。
\(dp[i]=\max (\sum_{\text{for all v}}{dp[v]},-x-wei[i])\)
  答案为\(dp[1]+wei[1]\)
  复杂度\(O(n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1000005
struct Edge
{
    int fr,to;
}eg[maxn<<1];
long long wei[maxn],dp[maxn];
int head[maxn],edgenum,x,n;
inline void add(int fr,int to)
{
    eg[++edgenum]=(Edge){head[fr],to};
    head[fr]=edgenum;
}
inline void dfs1(int now,int fa)
{
    for(int i=head[now];i;i=eg[i].fr)
    {
        if(eg[i].to==fa) continue;
        dfs1(eg[i].to,now);
        wei[now]+=wei[eg[i].to];
    }
}
inline void dfs2(int now,int fa)
{
    for(int i=head[now];i;i=eg[i].fr)
    {
        if(eg[i].to==fa) continue;
        dfs2(eg[i].to,now);
        dp[now]+=dp[eg[i].to];
    }
    dp[now]=max(dp[now],-x-wei[now]);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&x);
        memset(head,0,sizeof(head));
        memset(dp,0,sizeof(dp));
        edgenum=0;
        for(int i=1;i<=n;++i)
            scanf("%lld",&wei[i]);
        int u,v;
        for(int i=1;i<n;++i)
            scanf("%d%d",&u,&v),add(u,v),add(v,u);
        dfs1(1,0),dfs2(1,0);
        printf("%lld\n",dp[1]+wei[1]);
    }
    return 0;
}

 E Dish Owner
  题意:有n个厨师,每个人最开始都有一个菜,分值为s[i]。m个操作,0 x y表示x和y对决,谁手下分值最大的菜的分值最大谁胜。败者的菜归胜者所有。
1 x 表示查询编号x的菜在谁手上。\(n,m\leq 10000\)
  题解:显然如果这个人没被淘汰则它的分值最大的菜必定就是它一开始的菜。并查集维护即可。

#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int fa[maxn],s[maxn];
inline int findf(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findf(fa[x]);
}
int main()
{
    int n,t,q,op,x,y;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&s[i]),fa[i]=i;
        scanf("%d",&q);
        for(int i=1;i<=q;++i)
        {
            scanf("%d",&op);
            if(op)
            {
                scanf("%d",&x);
                printf("%d\n",findf(x));
            }
            else
            {
                scanf("%d%d",&x,&y);
                int fx=findf(x),fy=findf(y);
                if(fx==fy)
                {
                    puts("Invalid query!");
                    continue;
                }
                if(s[fx]==s[fy]) continue;
                if(s[fx]<s[fy]) fa[fx]=fy;
                else fa[fy]=fx;
            }
        }
    }
    return 0;
}

 F Triplets
  题意:给定x,y,z三个序列,长度分别为p,q,r。
  求
\[(\sum_{i=1}^{p}{\sum_{j=1}^{q}{\sum_{k=1}^{r}{(x_{i}+y_{j})*(y_{j}+z_{k})*[x_{i}\leq y_{j}\&\&z_{k}<=y_{j}]}}})\mod 10^{9}+7\]
\(p,q,r\leq 10^{6}\)
  题解:对三个序列排序,式子可以变为
\[\sum_{i=1}^{q}{y_i^2+sum_x*sum_z+sum_x*num_z+num_x*sum_z}\]
其中sum_x表示x序列的前缀和,num_x表示符合条件的x的数目,这个用upper_bound就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 100005
#define ll long long
#define mod 1000000007
ll a[maxn],b[maxn],c[maxn],suma[maxn],sumc[maxn];
ll ans;
int main()
{
    //freopen("data.in","r",stdin);
    int t,p,q,r;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&p,&q,&r);
        for(int i=1;i<=p;++i)
            scanf("%lld",&a[i]);
        for(int i=1;i<=q;++i)
            scanf("%lld",&b[i]);
        for(int i=1;i<=r;++i)
            scanf("%lld",&c[i]);
        ans=0;
        sort(a+1,a+p+1);
        sort(b+1,b+q+1);
        sort(c+1,c+r+1);
        for(int i=1;i<=p;++i)
            suma[i]=suma[i-1]+a[i],suma[i]%=mod;
        for(int i=1;i<=r;++i)
            sumc[i]=sumc[i-1]+c[i],sumc[i]%=mod;
        for(int i=1;i<=q;++i)
        {
            int tp=upper_bound(a+1,a+p+1,b[i])-a-1;
            int tr=upper_bound(c+1,c+r+1,b[i])-c-1;
            ans+=(((b[i]*tp%mod)*b[i]%mod)*tr%mod);
            ans+=suma[tp]*sumc[tr]%mod;
            ans%=mod;
            ans+=((suma[tp]*tr+sumc[tr]*tp)%mod*b[i]%mod);
            ans%=mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

8.1 Day2 不是cc的题已标出来
 A Reign
  题意:给定序列,求所有相隔大于k的两个子串和的最大值。
  题解:先跑两遍最大子段和求出一个类似前缀和的东西(从前往后和从后往前),然后把从后往前的取个max,然后枚举分割点。具体看代码吧。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define maxn 100005
#define inf 0x3f3f3f3f3f
ll num[maxn],head[maxn],tail[maxn];
int main()
{
    int t,n,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;++i)
            scanf("%lld",&num[i]);
        memset(head,0,sizeof(head));
        memset(tail,0,sizeof(tail));
        for(int i=1;i<=n;++i)
            head[i]=head[i-1]<0?num[i]:head[i-1]+num[i];
        head[0]=-inf;
        for(int i=1;i<=n;++i)
            head[i]=max(head[i-1],head[i]);
        for(int i=n;i;--i)
            tail[i]=tail[i+1]<0?num[i]:tail[i+1]+num[i];
        tail[n+1]=-inf;
        for(int i=n;i;--i)
            tail[i]=max(tail[i+1],tail[i]);
        ll ans=-inf;
        for(int i=1;i+k<n;++i)
            ans=max(ans,head[i]+tail[i+k+1]);
        printf("%lld\n",ans);
    }
    return 0;
}

 B CF 1037E Trips
  题意:n个人,m天。最开始所有人都没有友谊。每一天都会有一对新的友谊关系。如果某一天有一群人,每个人都有k个朋友在其中,则这群人可以一起出去玩。求每一天能出去玩的人的数量。
注意:友谊不具有传递性。
  题解:考虑删边。当一个人的度数(入度+出度)小于k是删掉这个点并更新与之相连的点的度数。

#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
set<int> s[maxn];
#define it set<int>::iterator
int cnt,deg[maxn],ans[maxn],vis[maxn];
int n,m,k;
int fr[maxn],to[maxn];
void del(int x)
{
    if(vis[x]||deg[x]>=k) return;
    queue<int> q;
    q.push(x);
    vis[x]=1;
    --cnt;
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        for(it i=s[tmp].begin();i!=s[tmp].end();++i)
            if(--deg[*i]<k&&!vis[*i])
            {
                q.push(*i);
                vis[*i]=1;
                --cnt;
            }
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    int u,v;
    cnt=n;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&u,&v);
        ++deg[u],++deg[v];
        s[u].insert(v),s[v].insert(u);
        fr[i]=u,to[i]=v;
    }
    for(int i=1;i<=n;++i) del(i);
    ans[m]=cnt;
    for(int i=m;i>1;--i)
    {
        if(!vis[to[i]]) --deg[fr[i]];
        if(!vis[fr[i]]) --deg[to[i]];
        s[fr[i]].erase(to[i]);
        s[to[i]].erase(fr[i]);
        del(fr[i]);
        del(to[i]);
        ans[i-1]=cnt;
    }
    for(int i=1;i<=m;++i)
        printf("%d\n",ans[i]);
    return 0;
}

 C CF 475B Strongly Connected City
  题意:给定n条横边,m条竖边,问是否每一个交点都能到达其余任何一个交点。
  题解:可以用Tarjan。不过\(n,m\leq 20\)。显然直接对每个点bfs即可。

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define pa pair<int,int>
char s[30];
struct Edge
{
    int fr,to;
}eg[5005];
int head[1000],edgenum;
inline void add(int fr,int to)
{
    eg[++edgenum]=(Edge){head[fr],to};
    head[fr]=edgenum;
}
int vis[1000];
void bfs(int s)
{
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s);
    vis[s]=1;
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        for(int i=head[tmp];i;i=eg[i].fr)
        {
            if(vis[eg[i].to]) continue;
            vis[eg[i].to]=1;
            q.push(eg[i].to);
        }
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    scanf("%s",s+1);
    for(int i=1;i<=n;++i)
    {
        if(s[i]=='<')
            for(int j=m;j>1;--j)
                add((i-1)*m+j,(i-1)*m+j-1);
        else
            for(int j=1;j<m;++j)
                add((i-1)*m+j,(i-1)*m+j+1);
    }
    scanf("%s",s+1);
    for(int i=1;i<=m;++i)
    {
        if(s[i]=='v')
            for(int j=0;j<n-1;++j)
                add(j*m+i,(j+1)*m+i);
        else
            for(int j=n-1;j>0;--j)
                add(j*m+i,(j-1)*m+i);
    }
    for(int i=1;i<=n*m;++i)
    {
        bfs(i);
        for(int j=1;j<=n*m;++j)
            if(!vis[j])
            {
                puts("NO");
                exit(0);
            }
    }
    puts("YES");
    return 0;
}

 D Reach Equilibrium
  题意:有n个向量,随机向量模长使得模长之和为k。方向未定。求能使得这n个向量相加为0的概率。结果必定能写为\(p/q\)的形式。输出\(p*q^{-1}\mod 10^{9}+7\)
  题解:传送门

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll a,b,x,y;
inline void exgcd(ll a,ll b,ll& x,ll& y)
{
    if(b==0)
    {
        x=1,y=0;
        return;
    }
    exgcd(b,a%b,y,x);
    y-=a/b*x;
}
inline ll qpow(ll a,int x)
{
    ll ans=1;
    while(x)
    {
        if(x&1) ans=(ans*a)%mod;
        a=(a*a)%mod;
        x>>=1;
    }
    return ans;
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    ll tmp=qpow(2,n-1);
    exgcd(tmp,mod,x,y);
    x=(x%mod+mod)%mod;
    printf("%lld\n",(tmp-n+mod)%mod*x%mod);
    return 0;
}

 E Partition into Permutations
  题意:给定序列a。可以插入一个元素或删除一个元素。进行若干次修改后,可以使序列被划分为若干个集合。求修改次数最小值。集合定义为一个包含且仅包含一次\(1\sim n\)的序列。
  题解:设\(dp[i][j]\)表示i这个数应当出现j次。
\[dp[i][j]=min_{j\leq k}(dp[i-1][k])+abs(j-cnt_{i})\]
  其中cnt表示i在原序列中出现的次数。

#include<bits/stdc++.h>
using namespace std;
#define maxn 2000005
int a[maxn],cnt[maxn];
vector<int> dp[maxn];

int main()
{
    int ans,t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        ans=0;
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=2*n;++i)
            dp[i].clear();
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&a[i]);
            if(a[i]>(n<<1)) ++ans;
            else ++cnt[a[i]];
        }
        n<<=1;
        for(int i=1;i<=n;++i)
            dp[0].push_back(0);
        for(int i=1;i<=n;++i)
        {
            for(int j=0;j<=n/i;++j)
                dp[i].push_back(dp[i-1][j]+abs(j-cnt[i]));
            for(int j=n/i-1;j>=0;--j)
                dp[i][j]=min(dp[i][j+1],dp[i][j]);
        }
        ans+=min(dp[n][0],dp[n][1]);
        printf("%d\n",ans);
    }
    return 0;
}

F Chef and Digit Jumps
  题意:给定一个数字组成的字符串。每个位置i可以跳到i-1,i+1,和序列中与i相同的数的位置。求第一个数到最后一个数至少要跳几次。
  题解:spfa(bfs应该也行)。

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
char s[maxn];
int dis[maxn],vis[maxn],n;
vector<int> numb[10];
bool visnum[10];
void spfa(int st)
{
    queue<int> q;
    memset(dis,0x3f,sizeof(dis));
    q.push(st);
    vis[st]=1;
    dis[st]=0;
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        if(tmp==n) break;
        vis[tmp]=0;
        if(tmp!=1&&dis[tmp-1]>dis[tmp]+1)
        {
            dis[tmp-1]=dis[tmp]+1;
            if(!vis[tmp-1])
            {
                vis[tmp-1]=1;
                q.push(tmp-1);
            }
        }
        if(tmp!=n&&dis[tmp+1]>dis[tmp]+1)
        {
            dis[tmp+1]=dis[tmp]+1;
            if(!vis[tmp+1])
            {
                vis[tmp+1]=1;
                q.push(tmp+1);
            }
        }
        if(visnum[s[tmp]-'0']) continue;
        visnum[s[tmp]-'0']=1;
        for(int i=0;i<(int)numb[s[tmp]-'0'].size();i++)
            if(dis[numb[s[tmp]-'0'][i]]>dis[tmp]+1)
            {
                dis[numb[s[tmp]-'0'][i]]=dis[tmp]+1;
                if(!vis[numb[s[tmp]-'0'][i]])
                {
                    vis[numb[s[tmp]-'0'][i]]=1;
                    q.push(numb[s[tmp]-'0'][i]);
                }
            }
    }
}
int main()
{
    scanf("%s",s+1);
    while(s[++n]!='\0')
        numb[s[n]-'0'].push_back(n);
    --n;
    spfa(1);
    printf("%d\n",dis[n]);
    return 0;
}

8.2 Day1
  T1 最大跨距(dis)
  题意:有三个字符串S,S1,S2。想检测S1和S2是否同时在S中出现,且S1位于S2的左边,并在S中互不交叉(即S1的右边界点在S2的左边界点的左侧)。计算满足上述条件的最大跨距(最右边的S2的起始点与最左边的S1的终止点之间的字符数目)。如果没有满足条件的S1,S2存在,则输出−1。
  题解:manacher模板
  不过scanf还能这么用……学到了

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
char s[maxn],s1[maxn],s2[maxn];
int next1[maxn],next2[maxn];
inline void pre(char* s,int* next)
{
    int j=0;
    for(int i=2;s[i];++i)
    {
        while(j&&s[i]!=s[j+1]) j=next[j];
        if(s[i]==s[j+1]) ++j;
        next[i]=j;
    }
}
int main()
{
    freopen("dis.in","r",stdin);
    freopen("dis.out","w",stdout);
    scanf("%[^,],%[^,],%[^,\n]",s+1,s1+1,s2+1);//读入字符串,当遇到^后面的字符时停止
    int len1=strlen(s1+1),len2=strlen(s2+1);
    int j=0,k=0,p1=0,p2=0;
    pre(s1,next1),pre(s2,next2);
    for(int i=1;s[i];++i)
    {
        while(j&&s[i]!=s1[j+1]) j=next1[j];
        while(k&&s[i]!=s2[k+1]) k=next2[k];
        if(s[i]==s1[j+1]) ++j;
        if(s[i]==s2[k+1]) ++k;
        if(j==len1&&!p1) p1=i;
        if(k==len2) p2=i;
    }
    if(p1&&p2&&p2>=len2+p1)
    {
        printf("%d\n",p2-len2-p1);
        return 0;
    }
    puts("-1");
    return 0;
}

T2 生日蛋糕(cake)
  题意:给定一个圆心为原点,半径为r的圆。给定n条直线,每根直线以两点坐标描述。所有数据均为整数。
  题解:咕了。
  代码?咕了。

T3 咖啡供应(coffee)
  题意:给定一棵树,在任意一点建一座咖啡站可使到这个点距离小于等于k的点被覆盖到。求覆盖所有点所需要的最少咖啡站数量。
  题解:贪心?不过考场上写挂了。
  先广搜,然后按广搜逆序求解。
  设\(dp[0][x]\)表示x点能覆盖到的最大深度,\(dp[1][x]\)表示能覆盖到x点的最大深度。
  转移方程看代码吧。

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define inf 0x3f3f3f3f
struct Edge
{
    int fr,to;
}eg[maxn<<1];
int head[maxn],edgenum;
int vis[maxn],dp[2][maxn],n,k,ans;
inline void add(int fr,int to)
{
    eg[++edgenum]=(Edge){head[fr],to};
    head[fr]=edgenum;
}
int q[maxn];
inline void bfs()
{
    int h=1,t=1;
    q[1]=1;
    while(h<=t)
    {
        int tmp=q[h++];
        vis[tmp]=1;
        for(int i=head[tmp];i;i=eg[i].fr)
            if(!vis[eg[i].to])
                q[++t]=eg[i].to;
    }
    for(int i=t;i;--i)
    {
        int tmp=q[i];
        vis[tmp]=0;
        dp[0][tmp]=-inf;
        for(int i=head[tmp];i;i=eg[i].fr)
        {
            int to=eg[i].to;
            if(!vis[to])
            {
                dp[0][tmp]=max(dp[0][tmp],dp[0][to]-1);
                dp[1][tmp]=max(dp[1][tmp],dp[1][to]+1);
            }
        }
        if(dp[1][tmp]<=dp[0][tmp]) dp[1][tmp]=-inf;
        if(dp[1][tmp]==k) dp[1][tmp]=-inf,dp[0][tmp]=k,++ans;
    }
    if(dp[1][1]!=-inf) ++ans;
}
int main()
{
    freopen("coffee.in","r",stdin);
    freopen("coffee.out","w",stdout);
    int u,v;
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;++i)
        scanf("%d%d",&u,&v),add(u,v),add(v,u);
    bfs();
    printf("%d\n",ans);
    return 0;
}

8.3 Day 2
  T1 军队(tarmy)题意:给定长度为n的序列\(s_{i}\),\(k\),求满足
\[\sum_{i=l}^{r}{s_{i}}>=k\&\& \forall l\leq i<j \leq r,\gcd(s[i],s[j])==1\]
  下\(r-l+1\)的最大值。若不存在则输出0。\(n\leq 10^{5},s_{i}\leq 10^{6},k\leq maxint\)
  题解:显然若\([l,r]\)不合法则\([l,r+1]\)必不合法。当合法时移动\(r\)并更新最大值,不合法时移动\(l\),若满足第一个条件则更新。判断合法则分解当前这个数的质因数,若某段区间某因子出现次数超过一则不合法。(合法指满足第二个条件)
  代码?咕咕咕
  复杂度:\(O(n\sqrt n)\)(好像也可以用什么pollard-rho做到\(O(n^{\frac{5}{4}})\)啊)
  由于数据过水,\(O(n^3\log n)\)也可通过此题,而且最大的一个点连0.1s都没有(设gcd的复杂度为log级别)

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005

int a[maxn], sum[maxn];
inline int gcd(int a, int b)
{
    int tmp;
    while (b) tmp = b, b = a % b, a = tmp;
    return a;
}
inline bool check(int l, int r)
{
    for (int i = l; i < r; ++i)
        for (int j = i + 1; j <= r; ++j)
            if (gcd(a[i], a[j]) != 1) return false;
    return true;
}
int main()
{
    freopen("tarmy.in", "r", stdin);
    freopen("tarmy.out", "w", stdout);
    int n, k;
    scanf("%d%d", &n, &k);
    register int i;
    for (i = 1; i <= n; ++i)
        scanf("%d", &a[i]), sum[i] = a[i] + sum[i - 1];
    int maxl = 0;
    for (register int l = 1, r = 1; l <= n; l++)
    {
        while (sum[r] - sum[l - 1] < k && r <= n) r++;
        if (!check(l, r)) continue;
        maxl = max(maxl, r - l + 1);
        for (++r; r <= n; ++r)
        {
            if (!check(l, r)) break;
            maxl = max(maxl, r - l + 1);
        }
    }
    printf("%d\n", maxl);
    return 0;
}

  T2 机器人采集金属(trobot)
  题意:给定一颗n节点有根树,路径有边权(代价)。有k个机器人,每个机器人可以在任意一点消失。问遍历所有点的代价的最小值。\(n\leq 5* 10^{4},k\leq 20\)
  题解:神仙树形dp。设\(dp[i][j]\)表示i节点j个机器人全都不回这个节点最少能省多少钱。显然最坏的情况是只有一个机器人,代价为所有边权和的两倍。
  \[dp[i][j]=\max (dp[i][j],dp[v][l]+dp[i][j-l]-(j-2)*val)\]
  其中\(v\)\(i\)的子节点,\(val\)\(i,v\)间的路径权值。\((j-2)*val\)是因为本来要走两遍,现在要走\(j\)遍。
  答案为\(sum*2-dp[s][k]\)\(sum\)为权值和。
  复杂度:\(O(nk^{2})\)

#include<bits/stdc++.h>
#pragma warning(disable:4576)
using namespace std;
#define maxn 50005
struct Edge
{
    int fr, to, val;
}eg[maxn << 1];
int head[maxn], edgenum, k;
inline void add(int fr, int to, int val)
{
    eg[++edgenum] = (Edge){ head[fr],to,val };
    head[fr] = edgenum;
}
int dp[maxn][21], vis[maxn], sum;
inline void dfs(int rt)
{
    vis[rt] = 1;
    for (int i = head[rt]; i; i = eg[i].fr)
    {
        int to = eg[i].to;
        if (vis[to]) continue;
        dfs(to);
        for (int l = k; l; --l)
            for (int j = 1; j <= l; ++j)
                dp[rt][l] = max(dp[rt][l], dp[rt][l - j] + dp[to][j] - (j - 2) * eg[i].val);
    }
}
int main()
{
    freopen("trobot.in","r",stdin);
    freopen("trobot.out","w",stdout); 
    int n, s, u, v, w;
    scanf("%d%d%d", &n, &s, &k);
    for (int i = 1; i < n; ++i)
    {
        scanf("%d%d%d", &u, &v, &w);
        sum += w;
        add(u, v, w);
        add(v, u, w);
    }
    dfs(s);
    printf("%d\n", sum * 2 - dp[s][k]);
    return 0;
}

  T3 扔石头(tstones)
  题意:n块石头,每个人可以拿走\(1\sim k\)块,拿走最后一块的人获胜。给定n,k,求后手会不会赢。多组数据。\(n \leq 10^{18},T\leq 10^{6}\)
  题解:懒得写了。
  复杂度:\(O(1)\)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
int main()
{
    freopen("tstones.in","r",stdin);
    freopen("tstones.out","w",stdout);
    int t;
    ull n,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%llu%llu",&n,&k);
        if(n%(k+1)!=0) puts("NO");
        else puts("YES");
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/123789456ye/p/11299248.html
今日推荐