Gym 101982 (2018-2019 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) )

传送门:

Problem A

温暖的签到题

#include
using namespace std;
const int maxn=1007;
char s1[maxn],s2[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,k,sum0=0,sum1=0;
    cin>>k>>s1>>s2;
    n=strlen(s1);
    for (int i=0;i<n;i++)
        if (s1[i]==s2[i]) sum1++;else sum0++;
    int ans=0;
    if (k<=sum1) ans=k+sum0;
    else ans=sum1+sum0-(k-sum1);
    cout<<ans<<endl;
    return 0;
}

Problem B

莫比乌斯反演大原题!

BZOJ2301的简化版,在那个题上只需要令 k = 1 k=1 k=1

#include 
using namespace std;
const int maxn=10000007;
bool check[maxn+10];
int prime[maxn+10];
int mu[maxn+10];
typedef long long ll;
void Moblus(){
    for(int i=0;i<=maxn;i++){
        check[i]=false;
    }
    mu[1]=1;
    int tot=0;
    for(int i=2;i<=maxn;i++){
        if(!check[i]){
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<tot;j++){
            if(i*prime[j]>maxn) break;
            check[i*prime[j]]=true;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;
                break;
            }
            else{
                mu[i*prime[j]]=-mu[i];
            }
        }
    }
}
int sum[maxn+10];
ll solve(int n,int m){
    ll ans=0;
    if(n>m) swap(n,m);
    for(int i=1,la=0;i<=n;i=la+1){
        la=min(n/(n/i),m/(m/i));
        ans+=(ll)(sum[la]-sum[i-1])*(n/i)*(m/i);
    }
    return ans;
}
int main()
{
    Moblus();
    sum[0]=0;
    for(int i=1;i<=maxn;i++){
        sum[i]=sum[i-1]+mu[i];
    }
    int a,b,c,d,k;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    k=1;
    ll ans=solve(b/k,d/k)-solve((a-1)/k,d/k)-solve(b/k,(c-1)/k)+solve((a-1)/k,(c-1)/k);
    printf("%lld\n",ans);
    return 0;
}

Problem C

题意:

给你 n n n个数,让你在其中选取 m m m个,要求这 m m m个数中不能有相同数字的数。现在问你方案数。

分析:

乍一看好像是一个组合数推公式的问题,但是我们发现,对于每一种数,都会存在取与不取两种状态,因此不好直接用组合数公式递推。

实际上,当我们把题目所给的所有的数都离散化之后,我们发现,这个问题就转化为:给你 n ′ n' n种数,每种数有 n u m i num_i numi个,现在要问在这 n ′ n' n种数中选 k k k个数的方案数。

因此我们可以把这个问题转化成 01 01 01背包问题。我们设 dp[i][j] \text{dp[i][j]} dp[i][j]为前 i i i种颜色中,有 j j j种颜色已经被取了的方案数。而对于当前的状态 dp[i][j] \text{dp[i][j]} dp[i][j],显然当前的状态是由前 i − 1 i-1 i1种颜色的取和不取这两种状态转移过来的。因此不难有状态转移方程 dp[i][j]=dp[i-1][j-1]*a[i]+dp[i-1][j] \text{dp[i][j]=dp[i-1][j-1]*a[i]+dp[i-1][j]} dp[i][j]=dp[i-1][j-1]*a[i]+dp[i-1][j]

代码:
#include
using namespace std;
const int maxn=1e3+7;
typedef long long ll;
const int mod=998244353;
ll dp[maxn][maxn];
unordered_map<int,int>mp;
int a[maxn];
int main(){
   // freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,k,x,cnt=0;
    cin>>n>>k;
    for (int i=1;i<=n;i++) {
        cin>>x;
        if (!mp.count(x)) mp[x]=++cnt,a[cnt]=1;
        else a[mp[x]]++;
    }
    dp[0][0]=1;
    for (int i=1;i<=cnt;i++) dp[i][0]=1;
    for (int i=1;i<=cnt;i++){
        for (int j=1;j<=k;j++) {
            dp[i][j]=(dp[i][j]+dp[i-1][j-1]*a[i]%mod)%mod;
            dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
        }
    }

    cout<<dp[cnt][k]<<endl;
    return 0;
}

Problem D

题意:

给你一个数 k k k b b b,问你在 0 … 2 b 0\dots 2^b 02b内,被 k k k整除的数中内的二进制位的 1 1 1的个数。

分析:

要统计一段区间内的答案,我们比较常用的做法是通过数位 d p dp dp

但是在这个题中,因为 b b b最大为 128 128 128,而鉴于 cf \text{cf} cf int128 \text{int128} int128,因此我们无法采用状压的方法进行数位 d p dp dp

因此我们考虑直接对二进制数进行数位 d p dp dp

我们设 dp[pos][one][mo] \text{dp[pos][one][mo]} dp[pos][one][mo]代表,当前有 p o s pos pos个二进制位,且累计了 o n e one one 1 1 1,且此时的位数的倍数是 m o mo mo

之后我们只需要枚举 0 / 1 0/1 0/1两个数位,并不断累计 dfs(pos-1,one+i,(mo*2+i)%k) \text{dfs(pos-1,one+i,(mo*2+i)\%k)} dfs(pos-1,one+i,(mo*2+i)%k)即为答案。

#include 
using namespace std;
typedef long long ll;
const int mod=1e9+9;
ll dp[130][130][1007],p[130];
bool a[130];
int b,k;
ll dfs(int pos,int one,int mo){
    if (pos==-1) {
        if (mo==0) return one;
        return 0;
    }
    if (dp[pos][one][mo]!=-1) return dp[pos][one][mo];
    ll res=0;
    for (int i=0;i<=1;i++){
        res=(res+dfs(pos-1,one+i,(mo*2+i)%k))%mod;
    }
    dp[pos][one][mo]=res;
    return res;
}
int main()
{
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>k>>b;
    memset(dp,-1,sizeof dp);
    for (int i=0;i<b;i++) a[i]=1;
    cout<<dfs(b-1,0,0)<<endl;
    return 0;
}

Problem E

题意:

在一个 n ∗ m n*m nm的平面图中,一个匪徒位于 ( x B , y B ) (x_B,y_B) (xB,yB)处,现在他想要逃出这个图。现在警察想要设置路障阻碍匪徒逃跑。现在平面图中有 k k k种地形,在第 i i i种地形布置路障需要花费 v a l i val_i vali元。现在问你最少花费多少钱才能阻止匪徒逃跑。

分析:

如果直接暴力搜显然太过复杂,因此我们考虑如何进行转化。

我们发现,这个题的的目的是阻止匪徒逃跑,换句话说,就是阻止匪徒走到边界点。即,让倘若我们把匪徒看成源点,边界点看作汇点,则我们现在想要做的即是求出切断源点与汇点的最小花费。而这个即是最小割。

而在这个问题中,因为部分点含有的是点权而不是边权,因此我们考虑进行拆点。我们将能设路障的点拆成两个点,继而使得点权转化为边权,而其他的点我们就分别跟超级源点和超级汇点连一条流量为无穷大的边。之后我们只需要在我们所构建的图上跑最大流即为答案。

#include 
#define maxn 10005
using namespace std;
struct Node{
    int to,next,val,w;
}q[maxn];
int head[maxn],cur[maxn],cnt,dep[maxn],vis[maxn],sp,tp;
const int INF=0x3f3f3f3f;
void add_edge(int from,int to,int val){
    q[cnt].to=to;
    q[cnt].val=val;
    q[cnt].next=head[from];
    head[from]=cnt++;

    q[cnt].to=from;
    q[cnt].val=0;
    q[cnt].next=head[to];
    head[to]=cnt++;
}
bool bfs(){
    int vis[maxn];
    memset(dep,INF,sizeof(dep));
    memset(vis,0,sizeof(vis));
    queue<int>que;
    dep[sp]=0;
    que.push(sp);
    while(!que.empty()){
        int x=que.front();
        que.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(dep[to]>dep[x]+1&&q[i].val){
                dep[to]=dep[x]+1;
                if(vis[x]==0)
                que.push(to);
                vis[to]=1;
            }
        }
    }
    if(dep[tp]!=INF) return 1;
    else return 0;
}
int dfs(int u,int flow){
    int rlow=0;
    if(u==tp)return flow;
    for(int i=cur[u];i!=-1;i=q[i].next){
        int d=q[i].to;
        cur[u]=1;
        if(q[i].val&&dep[d]==dep[u]+1){
            if(rlow=dfs(d,min(flow,q[i].val))){
                q[i].val-=rlow;
                q[i^1].val+=rlow;
                return rlow;
            }
        }
    }
    return 0;
}
int Dinic(){
    int total=0,tt;
    int lowflow=0;
    while(bfs()){
        memcpy(cur,head,sizeof(head));
        while(lowflow=dfs(sp,INF)) total+=lowflow;
    }
    return total;
}
int a[maxn];
char str[maxn][maxn];
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
int main()
{
    int n,m,k;
    memset(head,-1,sizeof(head));
    cnt=0;
    scanf("%d%d%d",&m,&n,&k);
    for(int i=0;i<n;i++){
        scanf("%s",str[i]);
    }
    for(int i=0;i<k;i++){
        scanf("%d",&a[i]);
    }
    sp=2*n*m+100,tp=sp+1;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            int u=i*m+j;
            if(str[i][j]=='.'||str[i][j]=='B')
                add_edge(u,u+n*m,INF);
            else add_edge(u,u+n*m,a[str[i][j]-'a']);
            for(int k=0;k<4;k++){
                int ii=i+dx[k];
                int jj=j+dy[k];
                if(ii>=n||ii<0||jj>=m||jj<0)
                    add_edge(u+n*m,tp,INF);
                else add_edge(u+n*m,(ii*m+jj),INF);
            }
            if(str[i][j]=='B')
                add_edge(sp,u,INF);
        }
    }
    int res=Dinic();
    if(res>=INF) puts("-1");
    else cout<<res<<endl;
    return 0;
}


Problem F

复习了一边扫描线……

在扫描线中(假设我们顺着 x x x轴平行的线进行扫描),我们把一个矩形分为上下两条线,其中下线的值为 1 1 1,上线的值为 − 1 -1 1。在我们扫描每一条线之后,我们会对当前这条扫描线所映射的区间(即区间 [ x 1 , x 2 ] [x_1,x_2] [x1,x2])进行区间更新(区间更新 1 或 − 1 1或-1 11)。而每次扫描后,如果线段树维护的某个的映射区间的标记值大于 1 1 1,则说明当前当前这个区间能够对答案有贡献,则就将改段区间对应的值累加,并向根部更新。最后树顶所表示的值即为在原图矩形中横坐标能贡献的大小 s i z e size size,最后我们将这个大小乘上前后两条扫描线所加的纵坐标的大小即为对于两条扫描线下的答案贡献。

扫描线的难点在于离散化后各个数值之间的关系。

题意:

给你 n n n个矩形,让你求这 n n n个矩形中的被覆盖的次数为奇数的面积并。

分析:

因为有了,奇数次的限制,因此我们需要考虑如何在原来的扫描线的基础下进行更改。

我们发现,对于扫描线所映射的区间,倘若被更新奇数次,则该段区间所对应的值能对答案有贡献;否则则没有贡献。因此我们发现,在这个问题中,我们只需要对标记维护奇偶,如果当前区间的标记为奇,则当前所映射的答案能够贡献,反之则不能。因此我们只需要在把之前的区间加法更新改成区间异或更新,如果 lazy \text{lazy} lazy 1 1 1,则统计答案即可。

#include 
#define maxn 200005
using namespace std;
struct Node{
    int x1,x2,y,w;
    Node(){}
    Node(int _x1,int _x2,int _y,int _w){
        x1=_x1,x2=_x2,y=_y,w=_w;
    }
    bool operator <(const Node &b)const{
        return y<b.y;
    }
}q[maxn*2];
struct ST{
    int sum,lazy;
}tr[maxn<<2];
void push_up(int rt){
    tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
}
int S[maxn];
void push_down(int l,int r,int rt){
    if(tr[rt].lazy!=0){
        int mid=(l+r)>>1;
        tr[rt<<1].sum=S[mid]-S[l-1]-tr[rt<<1].sum;
        tr[rt<<1|1].sum=S[r]-S[mid]-tr[rt<<1|1].sum;
        tr[rt<<1].lazy^=1;
        tr[rt<<1|1].lazy^=1;
        tr[rt].lazy=0;
    }
}
void update(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r){
        tr[rt].sum=S[r]-S[l-1]-tr[rt].sum;
        tr[rt].lazy^=1;
        return ;
    }
    push_down(l,r,rt);
    int mid=(l+r)>>1;
    if(L<=mid) update(L,R,l,mid,rt<<1);
    if(R>mid) update(L,R,mid+1,r,rt<<1|1);
    push_up(rt);
}
int main()
{
    int n;
    scanf("%d",&n);
    int tot=0,cnt=0;
    for(int i=1;i<=n;i++){
        int x1,y1,x2,y2;
        if(x1>x2) swap(x1,x2);
        if(y1>y2) swap(y1,y2);
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        q[++cnt]=Node(x1,x2,y1,1);
        q[++cnt]=Node(x1,x2,y2,-1);
        S[++tot]=x1;
        S[++tot]=x2;
    }
    sort(q+1,q+1+cnt);
    sort(S+1,S+1+tot);
    tot=unique(S+1,S+1+tot)-S-1;
    long long res=0;
    for(int i=1;i<=cnt;i++){
        int l=lower_bound(S+1,S+1+tot,q[i].x1)-S;
        int r=lower_bound(S+1,S+1+tot,q[i].x2)-S;
        if(l<=r) update(l+1,r,1,tot,1);
        if(i!=cnt){
            res+=1ll*tr[1].sum*(q[i+1].y-q[i].y);
        }
    }
    printf("%lld\n",res);
    return 0;
}

Problem G

题意:

给你一个矩形以及矩形外的一个点 p p p,现在要以 p p p为圆心,半径为 r r r,现在求这个圆不与矩形相交的最大的半径。

分析:

分为三种情况进行讨论:

  1. 如果点 p p p x x x轴坐标在矩形的与 x x x轴平行的边的范围内,则显然答案为点 p p p到最近的一条平行于 x x x轴的距离。
  2. 如果点 p p p y y y轴坐标在矩形的与 y y y轴平行的边的范围内,则显然答案为点 p p p到最近的一条平行于 y y y轴的距离。
  3. 如果不属于上述两个条件,则答案为点 p p p到里它最近的顶点的距离。
代码:
#include 
using namespace std;
double eps=1e-8;
int sgn(double x){
    if(fabs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x=_x,y=_y;
    }
    double operator ^(const Point &b)const{
        return x*b.y-y*b.x;
    }
    double operator *(const Point &b)const{
        return x*b.x+y*b.y;
    }
    Point operator -(const Point &b)const{
        return Point(x-b.x,y-b.y);
    }
    double distance(Point p){
        return hypot(x-p.x,y-p.y);
    }
};
struct Line{
    Point s,e;
    Line(){}
    Line(Point _s,Point _e){
        s=_s;
        e=_e;
    }
    double length(){
        return s.distance(e);
    }
    double dispointtoline(Point p){
        return fabs((p-s)^(e-s))/length();
    }
};
int main()
{
    //freopen("in.txt","r",stdin);
    Point a,b,c;
    scanf("%lf%lf%lf%lf%lf%lf",&a.x,&a.y,&b.x,&b.y,&c.x,&c.y);
    Line l[4];
    l[0]=Line(b,Point(b.x,c.y));
    l[1]=Line(b,Point(c.x,b.y));
    l[2]=Line(c,Point(b.x,c.y));
    l[3]=Line(c,Point(c.x,b.y));
    double res;
    double x1=min(l[1].s.x,l[1].e.x);
    double x2=max(l[1].s.x,l[1].e.x);
    double y1=min(l[0].s.y,l[0].e.y);
    double y2=max(l[0].s.y,l[0].e.y);
    if(a.x<=x2&&a.x>=x1){
        res=1e18;
        res=min(res,l[1].dispointtoline(a));
        res=min(res,l[2].dispointtoline(a));
    }
    else if(a.y<=y2&&a.y>=y1){
        res=1e18;
        res=min(res,l[0].dispointtoline(a));
        res=min(res,l[3].dispointtoline(a));
    }
    else{
        double tmp=a.distance(b);
        double tmp1=a.distance(c);
        double tmp2=a.distance(Point(b.x,c.y));
        double tmp3=a.distance(Point(c.x,b.y));
        res=min(tmp,min(tmp1,min(tmp2,tmp3)));
    }
    printf("%.3f\n",res);
    return 0;
}

Problem H

把素数筛出来,模拟一下题目的内容……然后就过了?

#include 
using namespace std;
const int maxn=1000005;
int prime[maxn+1];
bool p[maxn+1];
void getPrime(){
    memset(prime,0,sizeof(prime));
    for(int i=2;i<=maxn;i++){
        if(!prime[i]) prime[++prime[0]]=i,p[i]=1;
        for(int j=1;j<=prime[0]&&prime[j]<=maxn/i;j++){
            prime[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    getPrime();
    int n;
    cin>>n;
    int ans=0,x=n;
    while (x>=4){
        ans++;
        for (int j=x;j>=2;j--)
            if (p[j]&&p[x-j]) {
                x=j-x+j;
                break;
            }
    }
    cout<<ans<<endl;
    return 0;
}

Problem I


Problem J

温暖的签到题

代码:
#include 
using namespace std;

int main()
{
    int n,s;
    cin>>n>>s;
    int maxx=0;
    for(int i=1;i<=n;i++){
        int num;
        cin>>num;
        maxx=max(maxx,num);
    }
    maxx*=s;
    int res=(maxx+999)/1000;
    cout<<res<<endl;
}

Problem K


Problem L

温暖的签到题+1

#include 
using namespace std;
const int maxn=1007;
int a[maxn],b[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,ans=-1;
    cin>>n;
    for (int i=1;i<=n;i++) cin>>a[i]>>b[i];
    for (int i=0;i<=n;i++){
        int sum=0;
        for (int j=1;j<=n;j++)
            if (a[j]<=i&i<=b[j]) sum++;
        if (i==sum) ans=max(ans,i);
    }
    cout<<ans<<endl;
    return 0;
}

Problem M

你可能感兴趣的:(Gym 101982 (2018-2019 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) ))