NOIP2018普及组题解

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/84098763

虽然已经是TG选手了,但是作为一个初中生还是要做做PJ哒>_<.

T1:

题目:luogu5015.

题目大意:将输入的字符中的小写字母、大写字母和数字的总数量输出.

水题,用getchar水过就可以了.

代码如下:

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

#define Abigail inline void
typedef long long LL; 

int sum;

Abigail into(){
  char c=getchar();
  while (c!=EOF){
    if (c<='Z'&&c>='A'||c<='z'&&c>='a'||c<='9'&&c>='0') ++sum;
    c=getchar();
  }
}

Abigail outo(){
  printf("%d\n",sum);
} 

int main(){
  into();
  outo();
  return 0;
}

T2:

题目:luogu5016.

题目大意:给定一个数组A,现在在第m个点前的都属于龙,后面的都属于虎,第m个不属于任意一方.现在给Ai加了s1,要求再给一个Aj加s2,使得两方气势差最小.其中一方的气势定义为\sum Ai*|m-i|.

其实这道题就是算出两方的气势,然后暴力枚举加入的位置就可以了.

不懂为什么一群人说被long long坑了,这不是明显要开long long吗...

代码如下:

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

#define Abigail inline void
typedef long long LL;

const int N=100000;
const LL INF=(1LL<<63)-1LL;      //写成1LL<<60的我被卡精度了

LL a[N+9],sum1,sum2,a1,a2,ans;
int n,m,p,v;

Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
  scanf("%d%d%lld%lld",&m,&p,&a1,&a2);
}

Abigail work(){
  a[p]+=a1;
  for (int i=1;i<m;++i)
    sum1+=a[i]*(m-i);
  for (int i=m+1;i<=n;++i)
    sum2+=a[i]*(i-m);
  ans=INF;v=1;
  for (int i=1;i<m;++i)
    if (ans>abs(sum1+a2*(m-i)-sum2)) ans=abs(sum1+a2*(m-i)-sum2),v=i;
  if (ans>abs(sum1-sum2)) ans=abs(sum1-sum2),v=m;
  for (int i=m+1;i<=n;++i)
    if (ans>abs(sum1-sum2-a2*(i-m))) ans=abs(sum1-sum2-a2*(i-m)),v=i;
}

Abigail outo(){
  printf("%d\n",v);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

T3:

题目:luogu5017.

题目大意:给定一辆车往返一趟的时间m,并且车的容量无限大.现在要送n个人,人i会在时刻ti到达车站,问车这n个人等车时间之和最小是多少.

据说可以以时间为状态,然后斜率优化一下可以做到O(max \left \{ t[i] \right \}),但是我不会.

我们可以设f[i]表示送前i个同学需要的最少等待时间,然后直接枚举从前面那个状态转移过来,可以做到O(n^2).

但是很明显这个算法是不满足最优子结构的,因为总等待时间最短不一定代表回到起点的时间点最早.

我们考虑先预处理两个数组c[i]和s[i],分别表示到时刻i时的人的数量与乘客到达时间的前缀和.

那么我们考虑设f[i]表示时刻i发一辆车时的最少总等待时间,然后枚举上一次的发车时刻j来转移,时间复杂度O(max \left \{ t[i] \right \}^2).

对于这个算法,我们可以列出方程:

f[i]=min_{j=0}^{i-m}(f[j]+i*(c[i]-c[j])-s[i]+s[j])

考虑一个状态f[i]从f[j]转移过来,可以发现若i-j>2m,明显可以让这两次发车之间多发一次车来使答案不会变得更劣,那么我们就可以将枚举j变成枚举时差来优化上面的算法,时间复杂度O(m*max \left \{ t[i] \right \}).

那么方程就变为:

f[i]=min_{j=m}^{2m}(f[i-j]+i*(c[i]-c[i-j])-s[i]+s[i-j])

继续考虑优化这个算法,发现一辆车的发车时间只会是t[i]+j,其中0\leq j\leq m.那么我们可以只计算有用的状态,不去计算无用的状态,也就是说我们枚举到一个状态f[i]时,判断这个状态是否有一个t[j],使得t[j]\leq i\leq m,具体可以通过c[i]-c[i-m]是否为0来判断.这样我们就可以做到O(nm^2+max \left \{ t[i] \right \}).

然后貌似还可以把这个算法优化一下,类似于斜率优化,可以做到O(nm+nlogn),但是我不会.

O(nm^2+max \left \{ t[i] \right \})算法代码如下:

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

#define Abigail inline void
typedef long long LL;

const int N=500,M=100,T=5*1000000;
const int INF=(1<<29)-1;

int n,m,t[N+9];
int c[T+9],s[T+9],mt;
//int f[N+9][M*2+9];
int f[T+9];

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i){
    scanf("%d",&t[i]);
    mt=max(mt,t[i]);
    ++c[t[i]];s[t[i]]+=t[i];
  }
}

Abigail work(){
  mt+=m-1;
  sort(t+1,t+1+n);
  for (int i=1;i<=mt;++i)
    c[i]+=c[i-1],s[i]+=s[i-1];
  for (int i=0;i<=mt;++i)
    f[i]=i*c[i]-s[i];
  for (int i=0;i<=mt;++i){
    if (!(c[i]-c[i-m])&&i>m){
      f[i]=f[i-m];      //避免一些麻烦的处理 
      continue;
    }
    f[i]=i*c[i]-s[i];
    for (int j=max(0,i-(m<<1));j<=i-m;++j)
      f[i]=min(f[i],f[j]+i*(c[i]-c[j])-s[i]+s[j]);
  }
}

Abigail outo(){
  int ans=INF;
  for (int i=t[n];i<=mt;++i)
    ans=min(ans,f[i]);
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

T4:

题目:luogu5018.

题目大意:给定一棵二叉树,要求一棵最大的子树,使得这棵子树翻转过后与原子树一模一样(点权、结构、大小等).

感觉很难的一道题,也没做过类似的题,看到题面吓懵了,PJ考的都是什么毒瘤题啊.

事实上,我们只要考虑以每个点为根,暴力判断这棵子树是否对称,并在判定的时候及时弹出.这样看起来是O(n^2)的,事实上是O(nlogn)的.

为什么呢?首先我们在遍历的时候,每次判定,我们只要一旦发现就够不同就会弹出,那么很显然最坏的情况肯定是当一棵树是完全二叉树的时候.而完全二叉树的深度是O(logn)的,我们在每一层都O(n)扫了一遍节点,所以时间复杂度为O(nlogn).

具体各种剪枝看代码:

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

#define Abigail inline void
typedef long long LL;

const int N=1000000;

int n,v[N+9],ls[N+9],rs[N+9],siz[N+9],ans;

void dfs(int k){
  siz[k]=1;
  if (ls[k]) dfs(ls[k]);
  if (rs[k]) dfs(rs[k]);
  siz[k]+=siz[ls[k]]+siz[rs[k]];
}

bool check(int r1,int r2){
  if (r1+r2==0) return true;      //若r1=r2=0,则匹配 
  if (v[r1]^v[r2]) return false;      //两边点权是否相同 
  if ((siz[ls[r1]]^siz[rs[r2]])||(siz[rs[r1]]^siz[ls[r2]])) return false;      //两边是否大小/结构对称 
  if (!check(ls[r1],rs[r2])) return false;      //若r1的左儿子与r2的右儿子不匹配 
  if (!check(rs[r1],ls[r2])) return false;      //若r1的右儿子与r2的左儿子不匹配 
  return true;
}

Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i) scanf("%d",&v[i]);
  for (int i=1;i<=n;++i){
    scanf("%d%d",&ls[i],&rs[i]);
    if (ls[i]==-1) ls[i]=0;
    if (rs[i]==-1) rs[i]=0;
  }
}

Abigail work(){
  dfs(1);
  for (int i=1;i<=n;++i)
    if (check(i,i)) ans=max(ans,siz[i]);
}

Abigail outo(){
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/84098763