2018-2019 ACM-ICPC, Asia Shenyang Regional Contest

状况

David不在,我跟Fashion两个人打,只A了两题(。)

J. How Much Memory Your Code Is Using?(签到)

solver: Artist
简单模拟,注意getline前有数字,要用getchar冲掉。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int check(string str,int len){
    int unit;
    while(1){
        if(str[0]=='b') {unit=1;break;}
        if(str[0]=='c') {unit=1;break;}
        if(str[0]=='i') {unit=4;break;}
        if(str[0]=='l'&&str[5]=='l') {unit=8;break;}
        if(str[0]=='_') {unit=16;break;}
        if(str[0]=='f') {unit=4;break;}
        if(str[0]=='d') {unit=8;break;}
        if(str[0]=='l') {unit=16;break;}
        break;
    }
    int st=0,ed=0;
    for(int i=0;i<len;++i){
        if(str[i]=='[') st=i+1;
        if(str[i]==']') ed=i-1;
    }
    if(st==0&&ed==0) return unit;
    int num=0;
    for(int i=st;i<=ed;++i){
        num*=10;num+=str[i]-'0';
    }
    return num*unit;
}

int main() {
    int t;scanf("%d",&t);
    int ca=0;
    while(t--){
        int n;scanf("%d",&n);
        ll ans = 0;
        string str;getchar();
        for(int c=1;c<=n;++c){
            getline(cin,str);
            int len = str.length();
            ans+=check(str,len);
        }
        printf("Case #%d: %lld\n",++ca,(ans+1024-1)/1024);
    }
}

C. Insertion Sort(组合数学)

solver: FashionSE

  • 题意:permutation(n),前k个数被排序成新数列,这个新数列的最长上升子序列长度为至少n-1.求一开始的permutation有多少种。
  • 思路:
  • (1)最长上升子序列长度为n: A(k,k)
  • (2)最长上升子序列长度为n-1:
  • (2.1)前k个中的最大数字为k:前面A(k,k),那么后面n-k个数,为[n-k,n]且满足最长上升子序列长度为n-1。找规律得种类
  • 数目=(n-k-1)^2,再乘上前k个的所有排序A(k,k)
  • (2.2)前k个中的最大数字为k+1:那么前k个任意一个可以与k+1交换,而这任意一个可以放在n-k所有位置。即k*(n-k)
  • (2.3)前k个中的最大数字大于k+1:前k个一定是[1,k-1]和这个数。k可以放在除了第k+1之外(与2.2重复)任意一个位置。(n-k-1)
  • 因此答案为ans = A ( k , k ) × ( 1 + ( n − k − 1 ) 2 + k × ( n − k ) + n − k − 1 ) A(k,k)\times(1+(n-k-1)^2+k\times(n-k)+n-k-1) A(k,k)×(1+(nk1)2+k×(nk)+nk1)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main(){
    int t;scanf("%d",&t);
    for(int cas = 1;cas<=t;++cas){
        ll n,k,q;
        ll ans = 1;
        scanf("%lld%lld%lld",&n,&k,&q);
        if(k>n-1) k = n-1;
        for(int i=1;i<=k;++i) ans=ans*i%q;
        ans=ans*((n-k-1)*(n-k-1)+(k+1)*(n-k))%q;
        printf("Case #%d: %lld\n",cas,ans);
    }
}

G. Best ACMer Solves the Hardest Problem(暴力)

  • 题意:四个操作,二维平面,有点权,加点、删点,查找某点周围距离\sqrt{k}的点权求和输出、查找并修改某点周围距离
  • k \sqrt{k} k 的点点权加w。case1e3,初始点数n1e5,操作数m1e5,要求在线,k1e7,xyw6e3。
  • 思路:(暴力)观察到,k1e7非常小(作为距离的平方)。因此,暴力出1~1e7每个数为结果的可能的平方组合,存在一个vector之中。
  • 然后,对于给定点,和给定距离,遍历该距离的可能组合,对每个组合,都在四个方向上进行试探。
#include <bits/stdc++.h>
using namespace std;
int t,cnt;
const int maxk = 1e7+6;
const int maxn = 1e5+5;
vector<pair<int,int>> G[maxk];
int vis1[10004];
int vis[6004][6004];
int val[6004][6004];
int px[maxn<<1],py[maxn<<1];
typedef long long ll;
ll ans,lastans;

int main(){
    scanf("%d",&t);
    for(int i=0;i<=1e4;++i){
        for(int j=0;j<=1e4;++j){
            if(i*i+j*j>1e7) break;
            if(i==j){
                if(vis1[i]) continue;
                else vis1[i]=1;
            }
            G[i*i+j*j].push_back(make_pair(i,j));
        }
    }
    for(int ca=1;ca<=t;++ca){
        printf("Case #%d:\n",ca);
        int n,m,opt,x,y;ll w,k;scanf("%d%d",&n,&m);
        lastans = cnt = 0;
        for(int i=1;i<=n;++i){
            scanf("%d%d%lld",&x,&y,&w);
            px[++cnt] = x,py[cnt] = y;
            vis[x][y] = 1,val[x][y] = w;
        }
        for(int c=1;c<=m;++c){
            scanf("%d%d%d",&opt,&x,&y);
            x = (x+lastans)%6000+1,y = (y+lastans)%6000+1;
            ans = 0;
            if(opt==1){
                scanf("%d",&w);
                px[++cnt] = x,py[cnt] = y;
                vis[x][y] = 1,val[x][y] = w;
            }
            if(opt==2){
                vis[x][y] = val[x][y] = 0;
            }
            if(opt==3){
                scanf("%lld%lld",&k,&w);
                for(auto d:G[k]){
                    int nx[3],ny[3];
                    nx[1] = x+d.first,nx[2] = x-d.first;
                    ny[1] = y+d.second,ny[2] = y-d.second;
                    for(int i=1;i<=2;++i){
                        if(nx[i]<1||nx[i]>6000) continue;  // 注意细节
                        if(i==2&&nx[i]==nx[i-1]) continue;
                        for(int j=1;j<=2;++j){
                            if(ny[j]<1||ny[j]>6000) continue;
                            if(j==2&&ny[j]==ny[j-1]) continue;
                            if(vis[nx[i]][ny[j]]) val[nx[i]][ny[j]]+=w;
                        }
                    }
                }
            }
            if(opt==4){
                scanf("%lld",&k);
                for(auto d:G[k]){
                    int nx[3],ny[3];
                    nx[1] = x+d.first,nx[2] = x-d.first;
                    ny[1] = y+d.second,ny[2] = y-d.second;
                    for(int i=1;i<=2;++i){
                        if(nx[i]<1||nx[i]>6000) continue;
                        if(i==2&&nx[i]==nx[i-1]) continue;
                        for(int j=1;j<=2;++j){
                            if(ny[j]<1||ny[j]>6000) continue;
                            if(j==2&&ny[j]==ny[j-1]) continue;
                            if(vis[nx[i]][ny[j]]) ans+=val[nx[i]][ny[j]];
                        }
                    }
                }
                printf("%lld\n",ans);
                lastans = ans;
            }
        }
        for(int i=1;i<=cnt;++i) vis[px[i]][py[i]] = val[px[i]][py[i]] = 0;
    }
}

K. Let the Flames Begin(约瑟夫环)

  • 题意:1~n的约瑟夫环,求第m个处死者的原序号,步长为k。k,n,m<=1e18,min(n,k)<=2e6
  • 思路:首先我们要知道约瑟夫环的递推公式:(约瑟夫环问题:处死第m个,求最后存活者的初始编号) 证明
  • 从0开始编号:f(n,m) = (f(n-1,m)+m)%n, f(1,m) = 0
  • 从1开始编号:f(n,m) = (f(n-1,m)+m)%n. f(1,m) = 0,最后答案 = f(n,m) + 1.

然后,考虑第m个处死者的原序号如何求得:
当进行到第m轮时,要处死第m个人,此时剩下(n-m+1)个人。
因此,f(n-m+1,m) = (k-1)%(n-m+1). 即,f代表,进行到剩余n-m+1个人时,该人的编号为k-1(以0为起点)。
然后递推出其初始编号。利用公式f(n,m) = (f(n-1,m)+m)%n,只不过起始条件有所改变。答案+1即可。
时间复杂度为O(m)。

但注意到,在这道题中,m<=1e18.
如果m<k,m<=2e6,可直接暴力;但如果m>k,m<=1e18,要寻找压缩这个递推过程的思路。
注意到,当m>k,此时有k<=2e6.此时,f(n-m+1,m) = (k-1)%(n-m+1)可能远小于n-m+2~n。
即,可能起始有一段递推过程,取模没有意义。
如此一来,就可以直接去掉这一段的取模操作,这一段的加法就可以被压缩为乘法。
为什么压缩这个过程就可以使得时间复杂度变安全了呢?参考
假若起始这一段可以被忽略取模的过程终止于第t次。
第0次:pos = (k-1)%(n-m+1)
第1次:pos2 = (pos+k)%(n-m+2)
第2次:pos3 = (pos+2k)%(n-m+3)

第t次:post = (pos+t
k)%(n-m+1+t)
如果pos+t*k<n-m+t+1,这整个过程都不需要取模。因为左边的增幅大于右边。往前走更不可能需要取模。
此时t<(n-m+1-pos)/(k-1)。t次加法可以被合并成1次乘法。
到第t+1次,需要取模,取模之后,又有可能遇到可以压缩的状况。
最差情况:n=m=1e18,k=2e6、此时t = tmin -> 0.最多需要操作数 = 55875734

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline int read(){
    char ch = getchar();int x=0,f=1;while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();return x*f;
}

int main(){
    int t = read(),cas = 0;
    while(t--){
        ll n,m,k;scanf("%lld%lld%lld",&n,&m,&k);
        ll ans;
        if(k==1) ans = m-1;
        else if(m<=k){
            ans = (k-1)%(n-m+1);
            for(ll i=n-m+2;i<=n;++i) ans = (ans+k)%i;
        }else{
            ans = (k-1)%(n-m+1);
            ll cur = n-m+1;
            while(1){
                ll tt = (cur-ans)/(k-1);
                if(tt*(k-1)==cur-ans) --tt;
                tt = min(tt,n-cur);
                ans += k*tt;
                cur += tt;
                ans=ans%cur;
                if(cur==n) break;
                ++cur;
                ans=(ans+k)%cur;
                if(cur==n) break;
            }
        }

        printf("Case #%d: %lld\n",++cas,ans+1);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值