周末练习1( 2017 JUST Programming Contest 4.0)

A

题目大概:

给出n个数,求所有的连续区间(包含区间长度为1的区间)的数字相与之和。

如7  11   9

(7) + (7 & 11) + (7 & 11 & 9) + (11) + (11 & 9) + (9) = 40

思路:

这种区间的题正常来说都需要推规律,还是与数学有关。这道题的区间长度为1e5,暴力是不行的。如果做过类似的题目。就会把这些数转换成二进制的形式,题目数字最大为1e6,为2的20次方就够了。所以开一个二维数组,n行20多列,表示这些数字的二进制形式。

至于计算相与之和。那就是每列每列的找就可以了。因为是相与,区间链式连续的,所以找出连续的1就可以了。这些1组成的连续区间的数量,就是这些数可以组成的有效连续区间的数量。区间数量乘本列的二进制权值,就是和。

连续1的区间的数量的规律就是一个等差数列,一个长度为5的数列,组成的连续区间数量可以用5+4+3+2+1来表示。分表示长度为1区间数,长度为2.。。。。然后这就是一个等差数列。

那么现在,该问题的所有地方都可以写了。

代码:

#include <bits/stdc++.h>
#define PI acos(-1.0)
#define ll long long
using namespace std;
const int MAX=1e5+10;
bool vis[MAX][25];
long long a[MAX];
long long bound[25];
int main()
{
    int c=1;
    for(int i=1;i<=24;i++)
    {
        bound[i]=c;
        c*=2;
    }
    int T;
    cin>>T;
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
        {
            scanf("%I64d",&a[i]);
        }
        for(int i=0;i<n;i++)
        {
            long long cnt=a[i];
            for(int j=1;j<=22;j++)
            {
                vis[i][j]=(cnt&1);
                cnt>>=1;
            }
        }
        long long ans=0;
        for(int i=1;i<=22;i++)
        {
            long long len=0;
            for(int j=0;j<n;j++)
            {
                if(vis[j][i])
                    len++;
                else
                {
                    ans+=(len*(len+1))/2*bound[i];
                    len=0;
                }
            }
            ans+=(len*(len+1))/2*bound[i];
        }
        cout<<ans<<endl;
    }
}
B



C
题目大概:
给出n个数,然后让每个数都在数列中找一个和他加和%1e9+7之后,最大的数。
思路:
这道题,n最大为1e5,所以n2不行,需要优化到nlogn。一般都是用二分什么的优化。
仔细分析一下这道题,数列中的数最大不超过1e9+7,所以两者加和最大不超过2e9+7,题目求%1e9+7后,最大的数,就是最接近1e9+7,或者2e9+7的数,那么可以sort一下,在区间中求两个二分,分别求最接近1e9+7和2e9+7.然后比较就可以了。
代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
struct node
{
    ll val;
    ll id;
}a[100005];
bool cmp(node s,node t)
{
    if(s.val==t.val) return s.id<t.id;
    return s.val<t.val;
}
ll n;
ll ans[100005];
ll search(ll begin,ll end,ll e)
{
    ll mid,left=begin,right=end;
    while(left<=right)
    {
        mid=(left+right)>>1;
        if(a[mid].val>e) right=mid-1;
        else left=mid + 1;
    }
    return right;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i].val);
            a[i].id=i;
        }
        sort(a+1,a+1+n,cmp);
        for(int i=1;i<=n;i++)
        {
            ll t=search(1,n,mod-a[i].val-1);
            if(a[i].id==a[t].id) t--;
            //cout<<t<<endl;
            if(t!=0)
            {
                ans[a[i].id]=(a[t].val+a[i].val)%mod;
                //cout<<ans[a[i].id]<<endl;
            }
            else ans[a[i].id]=0;
            //cout<<ans[a[i].id]<<endl;
            t=search(1,n,2*mod-a[i].val-1);
            if(a[i].id==a[t].id) t--;
            ans[a[i].id]=max(ans[a[i].id],(a[t].val+a[i].val)%mod);
        }
        for(int i=1;i<=n;i++) cout<<ans[i]<<" ";cout<<endl;
    }
}
D
题目大概:给出长度为n的字符串(只包含小写字母)m个询问,求区间l到r之间包含字母q的个数。(l r可以超过n,字符串自动加长(复制自身))。
思路:
这种题当然求一个前缀和,然后r的前缀和减去l-1的前缀和,就可以了。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int t;
char a[10005];
int dp[10005][30];
int n,q;
int l,r;
char b;
int sum;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		memset(dp,0,sizeof(dp));
		scanf("%d%d",&n,&q);
		cin>>a;
		dp[1][a[0]-'a']++;
		for(int i=2;i<=n;i++)
		{
			for(int j=0;j<26;j++)
			{
				dp[i][j]=dp[i-1][j];
			}
			dp[i][a[i-1]-'a']++;
		}

		while(q--)
		{
			sum=0;
			scanf("%d %d %c",&l,&r,&b);

			int ans1,ans2;

			ans1=dp[n][b-'a']*((l-1)/n)+dp[(l-1)%n][b-'a'];
			ans2=dp[n][b-'a']*(r/n)+dp[r%n][b-'a'];
			sum=ans2-ans1;
			printf("%d\n",sum);
		}
	}


	return 0;
 }

E


给出n行数,每行6个数,从每行选择一个数,使得所有数乘积为x,问方案数。

思路:
一看这道题,就感觉是dfs就完了,但是数据量n是14,而且是多组输入,所以需要优化。
可以把这个n行数分为两半,先查一半,把这一半的数的乘积存入map,并且记录乘积为i的方案数是多少,记为map【i】。然后搜索,另一半,只要找出(x/j)%1e9+7==i的情况,然后加和就行了。这样算出来的总和,就是总的方案数了,map中没存的数为0.有除法取模。用逆元来求就可以了。

代码:

暂无



F

题目大概:

给出n个数求一种数的个数。这种数前面的数都小于等于它,后面的数都大于等于它。求这种数的个数。

思路:
前缀最大一下。后缀最小一下。
然后扫一遍数组,找出符合条件的数的个数。

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=1100000;
int a[maxn];
int qima[maxn];
int houmi[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        memset(a,0,sizeof(a));
        memset(qima,0,sizeof(qima));
        memset(houmi,0,sizeof(houmi));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            qima[i]=max(a[i],qima[i-1]);
        }
        houmi[n+1]=11111111;
        for(int i=n;i>=1;i--)
        {
            houmi[i]=min(houmi[i+1],a[i]);
        }
        int ans=0;
        for(int i=2;i<n;i++)
        {
            if(a[i]>=qima[i]&&a[i]<=houmi[i])
            {
                ans++;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}


G
题目大概:
一个矩形四周缺了多少1,如果内部有这些1那就补上,没有就返回-1.
思路:
模拟一遍。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=55;
char a[maxn][maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
       int n,m;
       int c1=0,c2=0;
       scanf("%d%d",&n,&m);
      for(int i=0;i<n;i++)
       scanf("%s",a[i]);
       for(int i=0;i<n;i++)
         for(int j=0;j<m;j++)
          {

              if(i==0)
              {
                  if(a[i][j]=='0')
                  c1++;
              }
              else if(i==n-1)
              {
                  if(a[i][j]=='0')
                  c1++;
              }
              else if(j==0)
              {
                  if(a[i][j]=='0')
                    c1++;
              }
              else if(j==m-1)
              {
                   if(a[i][j]=='0')
                    c1++;
              }
              if(i>0&&i<n-1&&j>0&&j<m-1)
              {
                  if(a[i][j]=='1') c2++;
              }
          }

          if(c1>c2) printf("-1\n");
          else printf("%d\n",c1);
    }
    return 0;
}


H
题目大概:
给出n个数,要求从第1个到第n个,每个位置,可以向右走1个数,也可以向右走到最近的和本位置的数相同的数上。问最少走几步。
思路:
每个位置要么向右走1步,要么向右走很多步,dp。
或者bfs。

代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 5e5 + 10;
int num[maxn], pos[maxn];
int dp[maxn];

int main(){
    int t;  scanf("%d", &t);
    while(t--) {
        int n;  scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d", num + i);
        }
        memset(pos,-1,sizeof(pos));
        dp[1] = 0;
        pos[num[1]] = 1;
        for(int i = 2; i <= n;i++)
        {
            dp[i]=dp[i-1]+1;
            if(pos[num[i]]!=-1)
            {
                dp[i]=min(dp[i],dp[pos[num[i]]]+1);
            }
            pos[num[i]]=i;
        }
        printf("%d\n", dp[n]);
    }
    return 0;
}
I
题目大概:
给出n个数,求所有子区间的每个数的乘积之和。
思路:
与A题,从题意上很类似。
从数据量上看,n2的的dp会炸。根据组合数学的(a1+1)*(a2+1)=a1a2+a1+a2+1
这个dp的规律与它很相似dp【i】=dp【i-1】*(a【i】+1)+a【i】;不过没有+1.

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int maxn=110000;
ll a[maxn];
ll dp[maxn];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ll n;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        dp[1]=a[1]%mod;
        for(int i=2;i<=n;i++)
        {
            dp[i]=(dp[i-1]+((dp[i-1]*a[i])%mod+a[i])%mod)%mod;
        }
        cout<<dp[n]<<endl;
    }
}
J
题目大概:
给出n个字母(有重复),求由这些字母组成的串中的回文串数量。
思路:
组合方式太多,肯定要推一下规律。
首先n奇数的情况下,字符串中,26个英文字母只能由1个由奇数个才有解。
n偶数的情况,字符串中,26个英文字母,不能有奇数个的。
如果有解。
那么26个英文字母一定有偶数个,或者变为偶数个了。
因为数回文串。那就把它一分为2,只需要求一半数量的26的英文字母的全排列就行,直接套有重复元素的全排列公式。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
using namespace std;
ll n;
char s[30];
int vis[30];
ll f[30];
int main()
{
    f[0]=f[1]=1;
    for(ll i=2;i<=15;i++)
    {
        f[i]=f[i-1]*i;
        //cout<<f[i]<<endl;
    }
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld",&n);
        scanf("%s",s);
        memset(vis,0,sizeof(vis));
        for(int i=0;i<n;i++)
        {
            vis[s[i]-'a'+1]++;
        }
        ll e=0,sum=0;
        for(int i=1;i<=26;i++)
        {
            if(vis[i]%2==1) {e++;}
            sum+=(vis[i]/2);
        }
        if(e>1) {cout<<0<<endl;continue;}
        ll ans=f[sum];
        for(int i=1;i<=26;i++)
        {
            if(vis[i]!=0)
            {
                if(vis[i]>=2) ans=ans/f[vis[i]/2];
            }
        }
        cout<<ans<<endl;
    }
}



猜你喜欢

转载自blog.csdn.net/a1046765624/article/details/80301420