NOIP2018普及组复赛解析

421 篇文章 4 订阅
47 篇文章 1 订阅

T1:标题统计


题目大意

输入一个字符串,求字符串除了空格的字符个数


解题思路

这种考你会不会编程的题不会?


code

#include<cstdio>
#include<string>
#include<iostream>
using namespace std;
int ans;
string c;
int main()
{
    getline(cin,c);
    int l=c.size();
    for(int i=0;i<l;i++)
      ans+=(c[i]!=' '&&c[i]!='\n');
    printf("%d",ans);
}

T2:龙虎斗


题目大意

一个长度为n序列,被中间点m分成两半,m左边和m右边。
左边战斗力为
∑ i = 1 m − 1 ( m − i ) ∗ a i \sum_{i=1}^{m-1}(m-i)*a_i i=1m1(mi)ai
∑ i = m + 1 n ( i − m ) ∗ a i \sum_{i=m+1}^{n}(i-m)*a_i i=m+1n(im)ai
找到一个数值加 s 2 s2 s2,使两边的战斗力之差最小


解题思路

先处理好两边战斗力
暴力枚举位置。注意要用longlong


code

#include<cstdio>
#include<algorithm>
#define N 100010
#define lls long long
using namespace std;
lls n,c[N],m,p1,s1,s2,ans1,ans2,mins,mark;
int main()
{
    scanf("%lld",&n);
    for(lls i=1;i<=n;i++)
      scanf("%lld",&c[i]);
    scanf("%lld%lld%lld%lld",&m,&p1,&s1,&s2);
    c[p1]+=s1;
    for(lls i=1;i<m;i++)
      ans1+=c[i]*(m-i);
    for(lls i=m+1;i<=n;i++)
      ans2+=c[i]*(i-m);
    mark=0;
    mins=1e19;
    for(lls i=1;i<m;i++)
    {
        if(abs(ans1+s2*(m-i)-ans2)<mins)
        {
            mark=i;
            mins=abs(ans1+s2*(m-i)-ans2);
        }
    }
    for(lls i=m+1;i<=n;i++)
    {
        if(abs(ans1-ans2-s2*(i-m))<mins)
        {
            mark=i;
            mins=abs(ans1-ans2-s2*(i-m));
        }
    }
    if((mins==abs(ans1-ans2)&&mark<m)||mins<abs(ans1-ans2)) 
      printf("%lld",mark);
    else printf("%lld",m);
}

T3:摆渡车


题目大意

n个人,有不同的到达时间。一辆车,来回一次要 m    m i n m\ \ min m  min。安排一个来回时间,使所有人等待时间之和最小。


解题思路

我们可以发现m很小可是t却很大。所有我们不一定要从t入手。因为一个人会影响到他的只有之前的 m − 1    m i n m-1\ \ min m1  min
f i , j f_{i,j} fi,j表示第i个人等了j的等待时间总数。然后我们枚举i和枚举上一班车的最后一个人j。之后枚举那个人等了多久k。我们就可以计算出这个人上车最少等待时间
t j + k + m − t i t_j+k+m-t_i tj+k+mti
然后我们可以更新 f i , w f_{i,w} fi,w
f i , w = m i n { f i , w , f j , k + s j + 1 , i + ( i − j ) ∗ w } f_{i,w}=min\{f_{i,w},f_{j,k}+s_{j+1,i}+(i-j)*w\} fi,w=min{fi,w,fj,k+sj+1,i+(ij)w}
其中用 s i , j s_{i,j} si,j表示i到j的人都等第j个人上车需要的时间


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510
#define M 210
using namespace std;
int n,m,t[N],s[N][N],f[N][M],ans;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&t[i]);
	sort(t+1,t+1+n);
	for(int i=1;i<n;i++)//预处理s
	  for(int j=i+1;j<=n;j++)
	  	  for(int k=i;k<j;k++)
	  	    s[i][j]+=t[j]-t[k];
	memset(f,0x3f3f3f3f,sizeof(f));
	t[0]=-2e9;
	for(int i=0;i<=m;i++)//初始化
	{
		f[0][i]=0;
		f[1][i]=i;
	}
	for(int i=2;i<=n;i++)//dp
	  for(int j=0;j<i;j++)
	    for(int k=0;k<=m;k++)
	    {
	    	int w=t[j]+k+m-t[i];
	    	if(w>0)
	    	  f[i][w]=min(f[i][w],f[j][k]+s[j+1][i]+(i-j)*w);
	    	else//特判防越界
	    	  f[i][0]=min(f[i][0],f[j][k]+s[j+1][i]);
		}
	ans=2e9;
	for(int i=0;i<=m;i++)
	  ans=min(ans,f[n][i]);
	printf("%d\n",ans);
}

T4:对称二叉树


题目大意

一棵n个点的二叉树,每个点有不同的权值。一棵树对称就是整棵树的左右子节点互换之和长的和之前一样。求这棵树上最大的一颗对称二叉树。


解题思路

对于每个点我们给他两个特征值
z 1 i = l s o n i ∗ 2 + r s o n i + w i ∗ 4 z1_i=lson_i*2+rson_i+w_i*4 z1i=lsoni2+rsoni+wi4
z 2 i = l s o n i + r s o n i ∗ 2 + w i ∗ 4 z2_i=lson_i+rson_i*2+w_i*4 z2i=lsoni+rsoni2+wi4
lson:这个点有没有左子节点
rson:这个点有没有右子节点
然后先左后右的跑一边,记录跑到的点的z1和这颗子树包含的范围。
再先右后左的跑一边,记录跑到的点的z2和这颗子树包含的范围。
之后对于每个节点用字符串hash判断一下z1对于范围是否和z2对应范围相等,如果相等那么这棵子树就是一颗对称二叉树。

时间负责度: O ( n ) O(n) O(n)


code

#include<cstdio>
#include<algorithm>
#define N 1000010
#define p 10007
#define ull unsigned long long
using namespace std;
int sz[N],a1[N],b1[N],e1[N],a2[N],b2[N],e2[N],z[N],f[N];
int maxs,n,tot,ls[N],rs[N],w[N];
ull hash1[N],hash2[N],pows[N];
void read(int &x)
{
    char c;
    bool flag=false;
    while(c=getchar())
      if((c>='0'&&c<='9')||c=='-') break;
    if(c!='-')
      x=c-48;
    else flag=true;
    while(c=getchar())
      if(c>='0'&&c<='9') x=x*10+c-48;
      else break;
    if(flag) x=-x;
}
void dfs1(int x)
{
    sz[x]=1;
    a1[++tot]=z[x];
    b1[x]=tot;
    if(ls[x]!=-1)
      dfs1(ls[x]);
    if(rs[x]!=-1)
      dfs1(rs[x]);
    sz[x]+=sz[ls[x]]+sz[rs[x]];
    e1[x]=tot;
}
void dfs2(int x)
{
    if(x==-1) return;
    a2[++tot]=f[x];
    b2[x]=tot;
    if(rs[x]!=-1)
      dfs2(rs[x]);
    if(ls[x]!=-1)
      dfs2(ls[x]);
    e2[x]=tot;
}
ull hash1z(int l,int r)
{return hash1[r]-hash1[l-1]*pows[r-l+1];}
ull hash2z(int l,int r)
{return hash2[r]-hash2[l-1]*pows[r-l+1];}
bool check(int x)
{
    if(sz[ls[x]]!=sz[rs[x]]) return false;
    int l1=b1[x],r1=e1[x],l2=b2[x],r2=e2[x];
    if(hash1z(l1,r1)==hash2z(l2,r2)) 
      return true;
    return false;
}
int main()
{
    read(n);
    for(int i=1;i<=n;i++)
      read(w[i]);
    for(int i=1;i<=n;i++)
    {
        read(ls[i]);read(rs[i]);
        //fa[ls[i]]=fa[rs[i]]=i;
        z[i]=((ls[i]!=-1)<<1)+(rs[i]!=-1)+w[i]*4;
        f[i]=((rs[i]!=-1)<<1)+(ls[i]!=-1)+w[i]*4;
        //计算特征值
    }
    dfs1(1);//正搜
    tot=0;
    dfs2(1);//反搜
    pows[0]=1;
    for(int i=1;i<=n;i++)
    {
        pows[i]=pows[i-1]*p;
        hash1[i]=hash1[i-1]*p+a1[i];
        hash2[i]=hash2[i-1]*p+a2[i];
    }//字符串哈希
    for(int i=1;i<=n;i++)//枚举节点
      if(check(i))//判断相等
      	maxs=max(maxs,sz[i]);
    printf("%d",maxs);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值