第一次vj团队赛补题

Posted ruanbaitql

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一次vj团队赛补题相关的知识,希望对你有一定的参考价值。

第一次vj团队赛,平均题目难度不大,阅读难度大,我太菜了,直接爆零了QAQ

 

A题:Beer Barrels 签到思维题,细节难处理

a,b两个数字组成所有不同的k位数,然后问在这所有不同的k位数里数字c一共出现了多少次,结果%1000000007。

举例a=1,b=2,k=3时,能组成8个k位数:111 112 121 211 122 212 221 222,在这8个数里数字1出现了12次,数字2出现了12次,其他数字都是出现0次

可以推出,当a不等于b时,一共能组成2^k个数,其中a出现了2^k/2次,b出现了2^k/2次。

当a==b的情况容易漏掉,a==b时,只能组成1个数

#include<iostream>
#include<algorithm>
using namespace std;
const long long MOD = 1000000007;
long long ksm(long long n,long long p,long long c ){
    if(c == 1) return 0;
    long long ans = 1;
    long long thi = n%c;
    while(p){
        if(p&1){
            ans = ans * thi % c;
        }
        thi = thi*thi%c;
        p>>=1;
    }
    return ans;
}
int main()
{
    long long a,b,k,c;
    cin>>a>>b>>k>>c;
    if(a==c||b==c){
        if(k==0) cout<<0<<endl;
        else if(a!=b) cout<<k*ksm(2,k,MOD)/2%MOD<<endl;
        else cout<<k<<endl;
    }
    else cout<<0<<endl;
    return 0;
}

B题:Beer Bill 签到题,纯阅读

round up 不是四舍五入而是向上取整。。。

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
int main()
{
    string s;
    long long ans = 0;
    while(cin>>s){
        int b = 0;
        int i = 0;
        int cnt = 0;
        bool flag = false;
        while(s[i]>=0&&s[i]<=9){
            b*=10;
            b+=s[i]-0; 
            flag = true;
            i++;
        }
        if(flag) i+=2;
        for(i;s[i]==|;i++){
            cnt++;
        }
        if(cnt == 0) cnt=1;
        if(flag) ans+=b*cnt;
        else ans+=42*cnt;
    }
    int re = ans%10;
    if(re) ans+=10-re;
    cout<<ans<<",-"<<endl;
    return 0;
}

C题:Beer Coaster 计算几何

求矩形和圆的重叠面积

这题难死我了,我没学过计算几何

技术图片

把圆心和矩形的四个顶点连接起来,如图,那么图中这种情况矩形的面积就是三角形OCD+三角形OAC+三角形OAB-三角形OBD;如果圆心在矩形内部,那么矩形的面积就是这四个三角形面积之和;总之,矩形的面积可以用这四个三角形的面积表示出来,那么矩形和圆的重叠面积也可以用这四个三角形分别和圆的重叠面积表示出来。

那么问题就转化为求一个顶点在圆心上的三角形和圆的重叠面积。

情况一:三角形的两个不在圆心上的顶点都在圆内

技术图片

重叠面积就是三角形的面积

三角形的面积=两个边向量的叉乘1/2

用这种方法算三角形的面积。

 

情况二:一顶点在圆外

技术图片

如图,重叠面积=灰色扇形+绿色三角形

绿色三角形的面积要用连接圆心的两条边及其夹角算,S=1/2absinθ

 

θ有角1+角2和角2-角1两种情况,如图

角1和角2都用acos算出来(再这之前还要算出红色的高)

扇形的面积:S=1/2 θ r^2

我试过其他各种算角度和算绿色三角形的方法,都wa了。。。貌似都是用S=1/2absinθ算,θ用acos算会更好一点

 

情况三:两顶点都在圆外

技术图片

又分两种情况,如图,角2用acos和点积除以两边长算,角1用acos和高算,绿色三角形还是用1/2absinθ算;

 

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
using namespace std;
const double PI = acos(-1.0);
const double INF = 100000;
const double eps = 1e-12;
double tri(double x,double y,double r,double p1x,double p1y,double p2x,double p2y){
    double ans = 0;
    double l1xl1 = (p1x-x)*(p1x-x)+(p1y-y)*(p1y-y);
    double l2xl2 = (p2x-x)*(p2x-x)+(p2y-y)*(p2y-y);
    double l1 = sqrt(l1xl1);
    double l2 = sqrt(l2xl2);
    double l3;
    double h;
    if(p1x == p2x){
        h = p1x - x;
        l3 = p1y-p2y;
    }
    else{
        h = p1y-y;
        l3 = p1x-p2x;
    }
    if(l3<0) l3 = -l3;
    if(h<0) h=-h;
    double dj0 = (p1x-x)*(p2x-x)+(p1y-y)*(p2y-y);
    double dj1 = (p2x - p1x)*(x - p1x)+(p2y-p1y)*(y-p1y);
    double dj2 = (p1x - p2x)*(x - p2x)+(p1y-p2y)*(y-p2y);
    double thita = acos(dj0/l1/l2);
    double angle1 =acos(dj1/l2/l3);
    double angle2 = acos(dj2/l1/l3); 
    double s = ((p1x-x)*(p2y-y)-(p1y-y)*(p2x-x))*0.5;
    if(s<0) s=-s;
    if(s==0) return 0;
    double thita1 = acos(h/r);
    if(l1xl1<=r*r&&l2xl2<=r*r){//两顶点都在圆内 
        return s;
    }
    else if(l1xl1<=r*r){//顶点2在圆外 
        if(l1xl1==0) return 0.0;
        double thita2;
        if(dj1<eps){
            thita2 = acos(h/r)-acos(h/l1);
        }
        else thita2 = acos(h/r)+acos(h/l1);
        double thita3 = acos(h/l2)-acos(h/r);
        ans = l1*r*sin(thita2)*0.5;
        ans += r*r*thita3*0.5;
    }
    else if(l2xl2<=r*r){//顶点1在圆外 
        if(l2xl2==0 ) return 0.0;
        double thita2;
        if(dj2<eps) {
            thita2 = acos(h/r) - acos(h/l2);
        }
        else thita2 = acos(h/r)+acos(h/l2);
        double thita3 = acos(h/l1)-acos(h/r);
        ans = l2*r*sin(thita2)*0.5;
        ans+=r*r*thita3*0.5;
    }
    else{//两顶点都在圆外 
        double thita3 = 2*thita1;
        if(h>r-eps) ans = r*r*thita*0.5;
        else if(dj1>0&&dj2>0){
            ans = r*r*sin(thita3)*0.5;
            ans += r*r*(thita-thita3)*0.5;
        }
        else ans = r*r*thita*0.5;
    }
    return ans;
}
int main()
{
    double x,y,r,ax,ay,bx,by;
    cin>>x>>y>>r>>ax>>ay>>bx>>by;
    if(ax>bx) swap(ax,bx);
    if(ay>by) swap(ay,by);
    double ans = 0;
    if(x<ax) ans-=tri(x,y,r,ax,ay,ax,by);
    else ans+=tri(x,y,r,ax,ay,ax,by);
    if(y<ay) ans-=tri(x,y,r,ax,ay,bx,ay);
    else ans+=tri(x,y,r,ax,ay,bx,ay);
    if(x>bx) ans-=tri(x,y,r,bx,ay,bx,by);
    else ans+=tri(x,y,r,bx,ay,bx,by);
    if(y>by) ans-=tri(x,y,r,ax,by,bx,by);
    else ans+=tri(x,y,r,ax,by,bx,by);
    printf("%.12lf
",ans);
    return 0;
} 

D题:Beer Flood

不会,我太菜了

E题:Beer Game dp

这题阅读真难,比赛的时候读了一个小时+。。

给两串字符串,含若干字母和数字,求使两个字符串相同且不含数字所需的最少操作数

操作一:任选一个字母插入到任意位置

操作二:任选一个字母删掉

操作三:把数字换成对应个数的任意字母

 

先把所有数字都展开成对应个数的‘#‘, ‘#‘可以和任意字母对应

这就转换成了求两串字符串的差距问题,正着求差距,或者先求最长公共字串都行

#include<iostream>
#include<algorithm>
using namespace std;
long long dp[12000][3000];
int main()
{
    long long res = 0;
    string a,b,s1,s2;
    s1 = "",s2="";
    cin>>a>>b;
    int lena = a.length();
    int lenb = b.length();
    for(int i = 0;i<lena;i++){
        if(a[i]<=9&&a[i]>=0){
            int x = a[i]-0;
            for(int j =1;j<=x;j++) s1+=#;
            res++;
        }
        else s1+=a[i];
    }
    for(int i = 0;i<lenb;i++){
        if(b[i]<=9&&b[i]>=0){
            int x = b[i]-0;
            for(int j =1;j<=x;j++) s2+=#;
            res++;
        }
        else s2+=b[i];
    }
    //cout<<s1<<endl<<s2<<endl;
    int len1 = s1.length();
    int len2 = s2.length(); 
    dp[0][0] = 0;
    for(int i =1;i<=len1;i++) dp[i][0] = i;
    for(int i = 1;i<=len2;i++) dp[0][i] = i;
    for(int i =1;i<=len1;i++){
        for(int j =1;j<=len2;j++){
            if(s1[i-1]==s2[j-1]||s1[i-1]==#||s2[j-1]==#) dp[i][j] = dp[i-1][j-1];
            else dp[i][j] = min(dp[i-1][j],dp[i][j-1])+1;
        }
    }
    cout<<dp[len1][len2]+res<<endl;
    return 0;
}

F题:Beer Marathon 签到思维题

给n个啤酒及它们各自的位置(在一条直线上),欲使它们的位置成公差为k的等差数列,求最小的搬运啤酒的总距离,

 

先排序,排序后的数组a中第i小的数要搬运到等差数列第i小的位置,那么有些啤酒要往左搬,有些啤酒要往右搬,容易证明当要往左搬的啤酒数和要往右搬的啤酒数相差最小时,总搬运数最小,通过这个求出等差数列,然后再求答案

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
long long a[1000006];
long long b[1000006];
int main()
{
    long long n,k;
    cin>>n>>k;
    for(int i =1;i<=n;i++){
        scanf("%lld",&a[i]);
    }
    sort(a+1,a+n+1);
    for(int i = 1;i<=n;i++){
        b[i] = a[i]-i*k;
    }
    sort(b+1,b+n+1);
    long long dx = b[n/2+1];
    long long ans = 0,res = 0;
    //cout<<dx<<endl;
    for(int i = 1;i<=n;i++){
        res+=max(b[i]-dx,dx-b[i]);
    }
    cout<<res<<endl;
    return 0;
}

G题:Beer Mugs 思维题

给一串字母(字母是在a到t的范围内),要找到最长的连续子串,这个子串可以通过交换位置变成回文串。

 

 

连续的子串中,每个字母出现奇数次记为1,出现偶数次记为0,这样一共有2^20个状态。

计算每个前缀子串的状态,若前缀s[i]和前缀s[j]的状态一样,那么 i+1 到 j 的这一段连续子串里所有的字母都是出现偶数次,如果前缀s[i]和前缀s[j]的状态只相差一位,那么这段连续子串里只有一个字母是奇数个的,也还是能构成回文串。

每计算出一个新的状态,就记录这个状态第一次出现的位置。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int MAXN = 1<<20;
int vis[MAXN+7];
int main()
{
    int n, now = 0,ans = 0;
    memset(vis,-1,sizeof(vis));
    char c;
    cin>>n;
    vis[0] = 0;
    for(int i =1;i<=n;i++) {
        cin>>c;
        now^=(1<<(c-a));
        if(vis[now]!=-1){//之前出现过这个状态 
            ans=max(ans,i-vis[now]);
        }
        for(int j=0;j<20;j++){//找之前是否出现过只相差一位的状态 
            int tmp = now^(1<<j);
            if(vis[tmp]!=-1) ans = max(ans,i-vis[tmp]);
        }
        if(vis[now]==-1) vis[now] = i;//如果是新的状态,记录这个状态第一次出现的位置 
    }
    cout<<ans<<endl;
    return 0;
} 

H题:Screamers in the Storm 阴间模拟

sb题,这题把好多人卡了好久,题目意思很难搞懂

给一个n*m的图,模拟t回合后图的情况

每个回合:羊会向下移一格,如果是在最下面的一格会瞬移到最上面的一格,狼会右移一格,如果是在最右边的一格会瞬移到最左边的一格

光秃秃的土经过 3 回合就会 生草

 

狼和羊到了同一格,狼就会吃羊,这块土地就会变成骨头,永远也不能生草了

羊在草地上,羊就会吃草,草地就会变成光秃秃的土

5 回合没进食就会饿死,在原地变成骨头,这块土地也永远也不能生草了

狼 10 回合没进食就会饿死,在原地变成骨头,这块土地也永远也不能生草了

样例给的很怪,看了标程才知道,羊是先移动,再吃草,再生草

 

#include<iostream>
#include<algorithm>
using namespace std;
int grass[23][23],newgrass[23][23];
int ji_e[23][23], newji_e[23][23]; 
char graph[23][23], newgraph[23][23];
int t,n,m;
void out(){
    for(int i =1;i<=n;i++){
        for(int j =1;j<=m;j++){
            if(graph[i][j]==S||graph[i][j]==W) cout<<graph[i][j];
            else if(grass[i][j]>=3) cout<<#;
            else if(grass[i][j]==-1) cout<<*;
            else cout<<.;
        }
        cout<<endl;
    }
}
int main()
{
    cin>>t>>n>>m;
    for(int i =1;i<=n;i++){
        for(int j = 1;j<=m;j++){
            cin>>graph[i][j];
            grass[i][j] = 0;
        }
    }
    while(t--){        
        //cout<<endl;
        for(int i =1;i<=n;i++){
            for(int j =1;j<=m;j++){
                newgrass[i][j] = 0;
                newji_e[i][j] = 0;
                newgraph[i][j]=.;
                if(grass[i][j]!=-1) newgrass[i][j] = grass[i][j]+1; 
                else newgrass[i][j] = -1;
            }
        }
        for(int i =1;i<=n;i++){
            for(int j =1;j<=m;j++){
                if(graph[i][j]==S){
                    int dd = i+1;
                    if(dd == n+1) dd = 1;
                    newgraph[dd][j]=S;
                    newji_e[dd][j] = ji_e[i][j]+1;
                    if(grass[dd][j] >=3 ){//羊是先移动,再吃草,再长草 
                        newgrass[dd][j] = 0;
                        newji_e[dd][j] = 0;
                    }
                }
            }
        }
        for(int i = 1;i<=n;i++){
            for(int j =1;j<=m;j++){
                if(graph[i][j] == W){
                    int dd = j+1;
                    if(dd==m+1) dd = 1;
                    if(newgraph[i][dd]==S){
                        newji_e[i][dd] = 0;
                        newgraph[i][dd] = W;
                        newgrass[i][dd] = -1;
                    }
                    else {
                        newgraph[i][dd]=W;
                        newji_e[i][dd] = ji_e[i][j]+1;
                    }                    
                }
            }
        }
        for(int i = 1;i<=n;i++){
            for(int j =1;j<=m;j++){
                graph[i][j] = newgraph[i][j];
                grass[i][j] = newgrass[i][j];
                ji_e[i][j] = newji_e[i][j];
                if(ji_e[i][j]==5&&graph[i][j]==S){//饿死当轮就判 
                    graph[i][j] = .;
                    grass[i][j] =-1;
                }
                if(ji_e[i][j]==10&&graph[i][j]==W){
                    graph[i][j] = .;
                    grass[i][j] = -1;
                }
            }
        }
        //out();
    }
    out();
    return 0;
} 

I题:Sixpack 思维题

给一个两行n列的网格,这个网格里填了m个数,每个空网格可以填一个数(0到9),要使这个网格所有 连续的 3 列里的数字之和 等于k,问有多少种填法。

分析不难得出:第1 列的数字之和和第4,7,10...列的一样,第2列的数字之和和第5,8,11...列的一样。

那么就枚举前三列的所有情况,每种情况都计算有多少种填法

#include<iostream>
#include<algorithm>
using namespace std;
int n,k,m;
int mx[2][100006];
int f[19]={1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1};
const long long MOD = 1000000007;
long long ans = 0;
long long qiu(int red,int blue,int green){
    long long res = 1;
    for(int i =1;i<=n;i+=3){
        if(mx[0][i]==-1&&mx[1][i]==-1){
            res*=f[red];
            res%=MOD;
        }
        else if(mx[0][i]!=-1&&mx[1][i]!=-1){
            if(mx[0][i]+mx[1][i] == red) continue;
            else return 0;
        }
        else {
            int yi;
            if(mx[0][i]!=-1) yi = mx[0][i];
            else yi = mx[1][i];
            if(red-yi>=0&&red-yi<=9) continue; 
            else return 0;
        }
    }
    for(int i = 2;i<=n;i+=3){
        if(mx[0][i]==-1&&mx[1][i]==-1){
            res*=f[blue];
            res%=MOD;
        }
        else if(mx[0][i]!=-1&&mx[1][i]!=-1){
            if(mx[0][i]+mx[1][i] == blue) continue;
            else return 0;
        }
        else {
            int yi;
            if(mx[0][i]!=-1) yi = mx[0][i];
            else yi = mx[1][i];
            if(blue-yi>=0&&blue-yi<=9) continue; 
            else return 0;
        }
    }
    for(int i = 3;i<=n;i+=3){
        if(mx[0][i]==-1&&mx[1][i]==-1){
            res*=f[green];
            res%=MOD;
        }
        else if(mx[0][i]!=-1&&mx[1][i]!=-1){
            if(mx[0][i]+mx[1][i] == green) continue;
            else return 0;
        }
        else {
            int yi;
            if(mx[0][i]!=-1) yi = mx[0][i];
            else yi = mx[1][i];
            if(green-yi>=0&&green-yi<=9) continue; 
            else return 0;
        }
    }
    return res;
}
int main()
{
    cin>>n>>k>>m;
    int r,c,v;
    for(int i =1;i<=n;i++){
        mx[0][i] = mx[1][i] = -1;
    }
    for(int i =1;i<=m;i++){
        cin>>c>>r>>v;
        c++;
        mx[r][c] = v;    
    }
    for(int red = 0;red<=18;red++){
        for(int blue = 0;blue<=18;blue++){
            int green = k-red-blue;
            if(green>=0&&green<=18){
                long long res = qiu(red,blue,green);
                //cout<<red<<" "<<blue<<" "<<green<<" "<<res<<endl;
                ans+=res;
                ans%=MOD;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
 } 

J题:Beer Vision 签到题

比赛的时候题目意思不敢确定,还是挺考验阅读理解的

由于酒喝多了出了幻觉,看到的图像都是真实的图像和虚像重叠而成,虚像是真实的图像按某个向量平移而成,现在你看到了 n 个点,问有多少个向量是可能的

枚举从一个点到其他所有点的向量,检查它们是否合法即可

注意一个向量合法,其反向量也合法,枚举了一个向量后,之后可能还会枚举到其反向量,所以先找到最左下的点,再用它枚举向量

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
struct PO{
    int x, y;
}po[1003];
bool vis[2003][2003];
int n,cnt = 0;
bool cmp(PO a, PO b){//排序,确保每个向量只check一次 
    if(b.x>a.x) return true;
    else if(b.x<a.x) return false;
    else return b.y>a.y;
}
bool check(int dx,int dy){
    for(int i =1;i<=n;i++){
        bool flag = false;
        int xx = po[i].x + dx ,yy = po[i].y + dy;
        if(xx>=0&&xx<=2000&&yy>=0&&yy<=2000&&vis[xx][yy]) flag = true;
        else{
            xx = po[i].x - dx,yy = po[i].y - dy;
            if(xx>=0&&xx<=2000&&yy>=0&&yy<=2000&&vis[xx][yy]) flag = true;
        }
        if(!flag) return false;
    }
    return true;
}
int main()
{
    cin>>n;
    for(int i =1;i<=n;i++) {
        cin>>po[i].x>>po[i].y;
        po[i].x+=1000;//离散化 
        po[i].y+=1000;
        vis[po[i].x][po[i].y] = true;    
    }
    sort(po+1,po+n+1,cmp);
    for(int i = 2;i<=n;i++){
        int dx = po[i].x - po[1].x;
        int dy = po[i].y - po[1].y;
        if(check(dx,dy)) cnt+=2;//两个方向 
    }
    cout<<cnt<<endl;
    return 0;
}

 

以上是关于第一次vj团队赛补题的主要内容,如果未能解决你的问题,请参考以下文章

牛客多校训练赛补题

2016CCPC东北赛补题

4/16 省赛补题

[水]浙大校赛补题

NC月赛补题

2018/11/30 周五集训队第七次测试赛补题题解