当前位置: 代码迷 >> 综合 >> [赛后总结]COCI2016/2017 Round#3题解
  详细解决方案

[赛后总结]COCI2016/2017 Round#3题解

热度:108   发布时间:2023-10-22 21:54:37.0

文章目录

  • Imena
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Pohlepko
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Kronican
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Kvalitetni
    • 题面
    • 题解
    • 实现
  • Zoltan
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解
    • 实现
  • Meksikanac
    • 题面
      • 描述
      • 输入
      • 输出
      • 分数分布
    • 题解

Imena

题面

描述

Mirko喜欢打字,由于他上课时经常感到无聊,所以他的老师分配给他一项任务。
Mirko必须重打一本包含NNN个句子的书,句子之间用空格分隔。在这本书中,每个句子由一个或多个用空格分隔的单词组成,其中只有最后一个单词的最后一个字符是标点符号(.?或者!)。其余单词不包含标点符号。
单词是一个字符数组,可能包含小写或大写的英文字母、数字或标点符号,其中标点符号只会出现在句子最后一个单词的末尾。
虽然Mirko喜欢打字,但他不喜欢打名字。名字是指除第一个字符是大写英文字母之外其他字符都是小写英文字母的单词,但最后一个字符除外,名字允许标点符号作为单词的最后一个字符。
在他决定开始重打之前,Mirko想知道书中每一句话有多少个名字。写一个程序来帮助他!

输入

第一行输入包含整数NNN(1≤N≤51≤N≤51N5)。表示句子的数量。
第二行输入包含书中的NNN个句子。
保证书中的总字符不超过100010001000

输出

输出NNN行每行一个整数。第i行的整数表示第i句话中有多少个名字。

分数分布

对于40%的数据,N=1N=1N=1

题解

奇怪的字符串处理题。
最好的方法是一个一个字符串输入依次判断,如果串中有标点就输出。
然而蒟蒻脑袋短路,于是成了一个一个字符输入…差点被Linux的两个换行断送了80分…
注意单独的大写字母也算作名字。
复杂度:O(L)O(L)O(L)

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int T;
char c;
int main()
{
    //freopen("imena.in","r",stdin);//freopen("imena.out","w",stdout);scanf("%d",&T);while(T--){
    int cnt=0,p1=0,p2=0;scanf("%c",&c);while(1){
    scanf("%c",&c);if(c>='A'&&c<='Z'&&!p2)p1++;else if(c>='a'&&c<='z'&&p1==1)p2++;else if(c==' ')cnt+=(p1==1),p1=0,p2=0;else if(c=='.'||c=='?'||c=='!'){
    cnt+=(p1==1),p1=0,p2=0;break;}else p1=p2=-1;}printf("%d\n",cnt);}
}

Pohlepko

题面

描述

Greedy得到了一块棋盘作为生日礼物。棋盘有NNNMMM列,每个格子中都有一个小写英文字母。在他的生日聚会上,每个人都很无聊,所以他们决定用棋盘玩一个简单的游戏。
首先在左上角标有坐标(1,1)(1,1)(1,1)的格子放置一个棋子。在每一个回合中,我们必须将芯片向右或向下移动一格,前提是没有移出棋盘。游戏结束时,将棋子移动到标有坐标(N,M)(N,M)(N,M)的格子即棋盘的右下角。在游戏中,我们依次记下移动棋子时经过的格子里的字母,从而构造一个单词。游戏的目标是找到能构造出的字典序最小的单词。
那些成功地构造了字典序最小单词的玩家会得到一袋糖果作为奖励。Greedy愿意付出任何代价来赢得糖果,所以他要求你写一个程序,可以找到能构造出的字典序最小的单词。

输入

第一行输入包含整数NNNMMM(1≤N,M≤20001≤N,M≤20001N,M2000)。表示场地的行数和列数。
接下来NNN行每行包含一个字符串,每个字符串由MMM个小写英文字母组成。表示棋盘。

输出

输出一个字符串。表示能构造出的字典序最小的单词。

分数分布

对于50%的数据,每个格子下方和右方的格子中的字母是不同的。

题解

首先考虑50%,发现可选的路径只有一条,只需要每次选字典序更小的字母往下走就可以了。
现在考虑下方和右方的字母相同的情况,这时候就有两条路可以走了。
这种路径不断拓展情况很像BFS。
由于第kkk步合法的点一定满足n+m?2=kn+m-2=kn+m?2=k,可以直接枚举,因此不需要BFS。
每次我们将走到第kkk步合法的点拓展一下,将拓展的所有点做下标记,然后扫一遍拓展的点,将所表示的字母不满足最小字典序的点标记删除即可。
具体请见代码。
复杂度:O(nm)O(nm)O(nm)

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 2000
int n,m;
char mp[MAXN+5][MAXN+5],p;
int flag[MAXN+5][MAXN+5];
string ans;
int main()
{
    //freopen("pohlepko.in","r",stdin);//freopen("pohlepko.out","w",stdout);scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%s",mp[i]+1);flag[1][1]=1;ans=mp[1][1];for(int i=2;i<n+m;i++){
    p='z'+1;for(int j=1;j<=min(n,i-1);j++)if(j<=n&&i-j<=m&&flag[j][i-j]){
    flag[j+1][i-j]=1,flag[j][i-j+1]=1;if(mp[j+1][i-j]>='a'&&mp[j+1][i-j]<='z')p=min(p,mp[j+1][i-j]);if(mp[j][i-j+1]>='a'&&mp[j][i-j+1]<='z')p=min(p,mp[j][i-j+1]);}ans+=p;for(int j=1;j<=min(n,i);j++)if(j<=n&&i+1-j<=m&&flag[j][i+1-j]&&mp[j][i+1-j]!=p)flag[j][i+1-j]=0;}cout<<ans;
}

Kronican

题面

描述

Mislav有NNN个无限体积的杯子,每一个杯子中都有一些水。Mislav想喝掉所有的水,但他不想喝超过K杯水。Mistrav能做的就是将一个杯子中的水倒入另一个杯子中。
不幸的是,挑选哪两个杯子进行倒水操作对Mislav来说很重要,因为并非所有的杯子都离他一样远。更准确地说,从i号杯子向j号杯子倒水所付出的代价为Ci,jC_{i,j}Ci,j?
帮助Mislav找到他需要付出的总代价的最小值。

输入

第一行输入包含整数NNNKKK(1≤K≤N≤201≤K≤N≤201KN20)。表示水杯的总数和Mislav最多能喝多少杯。
接下来N行每行包含NNN个整数Ci,jC_{i,j}Ci,j?(0≤Ci,j≤1050≤C_{i,j}≤10^50Ci,j?105)。第i+1i+1i+1行的第jjj个整数表示从第iii个杯子第jjj个杯子倒水所需要付出的代价。保证Ci,iC_{i,i}Ci,i?等于0。

输出

输出一个整数。表示Mislav需要付出的总代价的最小值。

分数分布

对于40%的数据,N≤10N≤10N10

题解

状压dp模版题。
看到n≤20n≤20n20首先想到状压或者搜索。
考虑到一杯水倒入其它杯中后就不会再有水倒入,显然这是一个0/1状态。
因此设dp[S]dp[S]dp[S]为状态为SSS耗费的最小步数。
枚举两个非空的水杯暴力转移即可。
转移方程:dp[S+(1&lt;&lt;i)]=min{dp[S]+c[i][j]}dp[S+(1&lt;&lt;i)]=min\{dp[S]+c[i][j]\}dp[S+(1<<i)]=min{ dp[S]+c[i][j]}
复杂度:O(n2?2n)O(n^2*2^n)O(n2?2n)

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 20
#define INF 1000000000000000
#define LL long long
int n,k;
int c[MAXN+5][MAXN+5];
LL dp[1<<MAXN],ans;
int bitcnt(int S){
    int cnt=0;while(S){
    cnt++;S-=S&(-S);}return cnt;
}
int main()
{
    //freopen("kronican.in","r",stdin);//freopen("kronican.out","w",stdout);scanf("%d%d",&n,&k);for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&c[i][j]);for(int S=0;S<(1<<n);S++)dp[S]=INF;dp[0]=0;for(int S=0;S<(1<<n);S++){
    for(int i=0;i<n;i++)if(!(S&(1<<i)))for(int j=0;j<n;j++)if(i!=j&&!(S&(1<<j)))dp[S^(1<<i)]=min(dp[S^(1<<i)],dp[S]+c[i][j]);}ans=INF;for(int S=0;S<(1<<n);S++)if(bitcnt(S)>=n-k)ans=min(ans,dp[S]);printf("%lld",ans);
}

Kvalitetni

题面

质量算数表达式由乘号,括号,数字和加号组成。
质量算数表达式由以下方式定义:

  1. 质量表达式由一个小于或等于Z1的正实数组成,这种表达式具有以下形式:
    (X)(X)(X)
    例如,如果Z1=5,则(4)是质量表达式。
  2. 如果A1,A2,…,AkA_1,A_2,…,A_kA1?,A2?,,Ak?是质量表达式,使得2≤k≤K2≤k≤K2kK,且这些质量表达式的不超过ZkZ_kZk?,那么以下表达式也是质量表达式:
    (A1+A2+…+Ak)(A_1+A_2+…+A_k)(A1?+A2?++Ak?)
    (A1?A2?…?Ak)(A_1*A_2*…*A_k)(A1??A2???Ak?)

已知一个质量表达式,其中的数字用问号替换。
求这个表达式可能具有的最大值。

题解

简单模拟题。
首先我们必须要理解题目的意思(由于作者太懒请自行理解…)。
然后很容易发现,对于每个质量表达式,我们就是要在满足∑i=1kAi≤Zk\sum^{k}_{i=1} A_i≤Z_ki=1k?Ai?Zk?,AiA_iAi?小于等于AiA_iAi?表达式的最大值的条件下,找到当前表达式的最大值。
MiM_iMi?表示AiA_iAi?表达式的最大值,那么可以分为以下三种情况。

  1. 表达式为(?)(?)(?),此时表达式的最大值显然就为Z1Z_1Z1?
  2. 表达式为(A1+A2+…+Ak)(A_1+A_2+…+A_k)(A1?+A2?++Ak?),将每个表达式能够达到的最大值之和与ZkZ_kZk?取最小值即可。
    即为min{∑i=1kMi,Zk}min\{\sum^{k}_{i=1} M_i,Z_k\}min{ i=1k?Mi?,Zk?}
  3. 表达式为(A1?A2?…?Ak)(A_1*A_2*…*A_k)(A1??A2???Ak?),先考虑满足∑i=1kAi≤Zk\sum^{k}_{i=1} A_i≤Z_ki=1k?Ai?Zk?的条件,显然在每个数取Zkk\frac{Z_k}{k}kZk??时最大,然而这样就会出现Mi<ZkkM_i<\frac{Z_k}{k}Mi?kZk??的情况。考虑将MiM_iMi?排序,每次取最小值,如果出现了上述情况,我们就让AiA_iAi?取到MiM_iMi?,让之后的数尽量保持Zk?Mik?1\frac{Z_k-M_i}{k-1}k?1Zk??Mi??,不断尝试下去即可。

显然我们可以先处理表达式深的最大值,由深到浅依次处理,而这个可以直接用递归实现。
复杂度:O(n)O(n)O(n)

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXK 50
#define MAXN 1000000
#define DB double
int k,z[MAXK+5],np,len;
char s[MAXN+5];
DB dfs(int dep)
{
    DB p[MAXK+1];int cnt=0;int kdf=0;while(np<=len){
    np++;if(s[np]=='?')p[++cnt]=z[1];if(s[np]=='*')kdf=1;if(s[np]==')'){
    sort(p+1,p+cnt+1);DB sum=0;for(int i=1;i<=cnt;i++)sum+=p[i];sum=min(sum,(DB)z[cnt]);if(!kdf)return sum;else{
    DB res=1;int i;for(i=1;i<=cnt;i++)if(sum/(cnt-i+1)>p[i]){
    sum-=p[i];res*=p[i];}else break;for(int j=i;j<=cnt;j++)res*=sum/(cnt-i+1);return res;}}if(s[np]=='('){
    p[++cnt]=dfs(dep+1);}}return p[1];
}
int main()
{
    //freopen("kvalitetni.in","r",stdin);//freopen("kvalitetni.out","w",stdout);scanf("%d",&k);for(int i=1;i<=k;i++)scanf("%d",&z[i]);scanf("%s",s+1);len=strlen(s+1);np=0;printf("%.5f",dfs(0));
}

Zoltan

题面

描述

Marton的朋友Cero有一个包含NNN个正整数的数组。开始时,Cero在黑板上写上第一个数字,然后,他将第二个数字写在第一个数字的左边或右边,之后,他将第三个数字写在目前为止写下的所有数字的左边或右边,以此类推。当他写下全部NNN个数字后,会形成一个新的数组。
●Marton想知道新数组的最长严格递增子序列的长度。
●Marton还想知道这种最长严格递增子序列的数量。
更确切的说,如果所有能构建出的新数组中最长严格递增子序列的最长长度为MMM,则想Marton知道所有可以构建的每个新数组中长度为MMM的最长严格递增子序列的数目的总和。如果新数组使用不同的顺序构建,则称为不同的新数组。对于同一个新数组,如果两个最长严格递增子序列在至少一个位置上不同,则称为两个不同的最长严格递增子序列。
考虑到这样的子序列的数目非常大,只需求出答案对109+710^9+7109+7取模的结果。
Cero要求你来回答Marton的两个问题。

输入

第一行输入包含整数NNN(1≤N≤2?1051≤N≤2*10^51N2?105)。表示数组中有多少个数。
第二行输入包含NNN个正整数AiA_iAi?(1≤Ai≤1091≤A_i≤10^91Ai?109)。表示Cero原本的数组。

输出

输出两个整数,中间用空格分隔。分别表示最长严格递增子序列的长度和这种长度的最长严格递增子序列的的数目。

分数分布

对于30%的数据,N≤20N≤20N20
对于50%的数据,N≤1000N≤1000N1000

题解

毒瘤dp题。
先考虑解决第一个问题。
明显是一个dp可以解决的问题。考虑到原问题就是求一些数字反转到第一个数前面后求最长上升子序列,而反转过来的数字的最长上升子序列就是原数列选中数字的最长下降子序列,那么问题就转化为了求一个最长上升子序列和最长下降子序列且最长下降子序列中最大的数小于最长上升子序列中最小的数。
考虑如何满足这个条件,显然我们只需要控制最长上升子序列和最长下降子序列的左端点重合就可以了,由于数列是严格上升,可以证明没有重复元素的存在。
dp1[i],dp2[i]dp1[i],dp2[i]dp1[i],dp2[i]分别为左端点为iii的最长上升子序列和最长下降子序列的最大长度,cnt1[i],cnt2[i]cnt1[i],cnt2[i]cnt1[i],cnt2[i]分别为左端点为iii的最长上升子序列和最长下降子序列的最大长度条件下的子序列个数。
首先这个序列的长度显然为dp1[i]+dp2[i]?1dp1[i]+dp2[i]-1dp1[i]+dp2[i]?1,那么我们就能很快找到序列的最长长度LenLenLen
对于每个满足dp1[i]+dp2[i]?1=Lendp1[i]+dp2[i]-1=Lendp1[i]+dp2[i]?1=Len的位置,我们需要计算它对方案数的贡献。首先这里合法的串显然有cnt1[i]?cnt2[i]cnt1[i]*cnt2[i]cnt1[i]?cnt2[i]种,然后我们需要考虑其它数如何摆放。
这里必须要纠正题解的一处明显的错误,方案数并不是因为其它n?Lenn-Lenn?Len个数都有两种选择而使得方案要乘上2n?Len2^{n-Len}2n?Len,因为如果选择的串中没有a1a_1a1?的话,那么a1a_1a1?其实只有一种选择。实质上,它需要分类讨论:

  1. 如果选择的串中没有a1a_1a1?,那么最长上升子序列左端点iii的位置不为1,此时,iii就有在a1a_1a1?前面或者后面里两种选择,而串外除去a1a_1a1?的所有点都可以放在串的前面和后面,因此有两种选择的点的个数为n?Lenn-Lenn?Len,方案数为2n?Len2^{n-Len}2n?Len
  2. 如果选择的串中有a1a_1a1?,那么最长上升子序列左端点iii的位置为1,此时,iii由于是第一个放下的点,因此只有一种选择,而选中的串外的所有点都可以放在串的前面和后面,因此有两种选择的点的个数为n?Lenn-Lenn?Len,方案数为2n?Len2^{n-Len}2n?Len

综上,一个位置对答案的贡献为cnt1[i]?cnt2[i]?2n?Lencnt1[i]*cnt2[i]*2^{n-Len}cnt1[i]?cnt2[i]?2n?Len
然而直接计算最长上升子序列和最长下降子序列及其个数是O(n2)O(n^2)O(n2)的,显然我们需要一个O(nlog?n)O(n\log n)O(nlogn)的解法。考虑将AiA_iAi?离散化,用树状数组(Fenwick tree)维护dp,cntdp,cntdp,cnt两个值,在维护dpdpdp前缀最大值时同时维护方案数即可。
复杂度:O(nlog?n)O(n\log n)O(nlogn)

实现

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 200000
#define MOD 1000000007
#define LL long long
int n,A[MAXN+5],B[MAXN+5],pn;
int minn,maxx,len,ans;
LL pow2[MAXN+5],kdf;
struct nd{
    int a;LL b;nd(){
    a=b=0;}
}tree[2][MAXN+5];
nd dp[MAXN+5],dp2[MAXN+5];
nd add(nd A,nd B){
    if(A.a==B.a)A.b=(A.b+B.b)%MOD;if(B.a>A.a)A=B;return A;
}
void Insert(int x,int kdf,nd D){
    while(x<=pn&&x>0){
    tree[kdf][x]=add(tree[kdf][x],D);x=x+(kdf?-1:1)*(x&(-x));}
}
nd Query(int x,int kdf){
    nd res;while(x<=pn&&x>0){
    res=add(res,tree[kdf][x]);x=x+(kdf?1:-1)*(x&(-x));}return res;
}
int main()
{
    //freopen("zoltan.in","r",stdin);//freopen("zoltan.out","w",stdout);scanf("%d",&n);pn=n;for(int i=1;i<=n;i++)scanf("%d",&A[i]);for(int i=1;i<=n;i++)B[i]=A[i];sort(B+1,B+n+1);pn=unique(B+1,B+n+1)-B;for(int i=1;i<=n;i++)A[i]=lower_bound(B+1,B+pn,A[i])-B;pow2[0]=1;for(int i=1;i<=n;i++)pow2[i]=(pow2[i-1]*2LL)%MOD;for(int i=n;i>=1;i--){
    nd p=Query(A[i]+1,1),s;s.a=1,s.b=1;if(!p.a)dp[i]=s,Insert(A[i],1,s);else p.a++,dp[i]=p,Insert(A[i],1,p);}for(int i=n;i>=1;i--){
    nd p=Query(A[i]-1,0),s;s.a=1,s.b=1;if(!p.a)dp2[i]=s,Insert(A[i],0,s);else p.a++,dp2[i]=p,Insert(A[i],0,p);}for(int i=1;i<=n;i++)ans=max(ans,dp[i].a+dp2[i].a-1);for(int i=1;i<=n;i++)if(dp[i].a+dp2[i].a-1==ans)kdf=(kdf+((1LL*dp[i].b*dp2[i].b)%MOD)*pow2[n-ans]%MOD)%MOD;printf("%d %lld",ans,kdf);
}

Meksikanac

题面

描述

你知道酒店和汽车旅馆之间有什么区别吗?没错,差别在于生活在那里的苍蝇数量。Norman是美国最受欢迎的汽车旅馆之一的所有者,但他的母亲坚持把它变成一家酒店。这就是为什么Norman在2016年的圣诞礼物是一个KKK边形的苍蝇拍(一个杀戮装置)。
为了满足母亲的要求,Norman被迫站在一个有NNN个苍蝇的窗户前。由于Norman不想伤害苍蝇,他想知道有多少方式,他用苍蝇拍击打窗户,而不伤害一只苍蝇。
窗户是一个矩形,左下顶点的坐标是(0,0)(0,0)(0,0)。Norman击打窗户时,苍蝇拍的每个顶点都必须位于整数坐标上,飞行苍蝇拍必须位于窗户内的区域,如果苍蝇位于苍蝇拍的顶点,边或多边形内部,则苍蝇视为被伤害。

输入

第一行输入包含三个整数XpX_pXp?,YpY_pYp?(1≤Xp,Yp≤5001≤X_p,Y_p≤5001Xp?,Yp?500)和NNN(0≤N≤Xp?Yp0≤N≤X_p*Y_p0NXp??Yp?)。前两个整数表示窗户右上角的坐标,第三个整数表示苍蝇的数量。
接下来NNN行中的每一行包含两个整数xxx(0&lt;x&lt;Xp0&lt;x&lt;X_p0<x<Xp?)和yyy(0&lt;y&lt;Yp0&lt;y&lt;Y_p0<y<Yp?)。表示苍蝇的坐标。
接下来一行包含了一个整数KKK(3≤K≤100003≤K≤100003K10000)。表示多边形的边数。
接下来KKK行中的每一行包含两个整数AAABBB(?109≤A,B≤109-10^9≤A,B≤10^9?109A,B109)。
表示飞行苍蝇拍各个顶点的坐标。按输入依次连接KKK个点,得到的KKK边形就是苍蝇拍的形状。

输出

输出一个整数。表示不伤害苍蝇的情况下Norman有多少种用苍蝇击打窗户的方法。

分数分布

对于62.5%的数据,1≤Xp,Yp≤1001≤X_p,Y_p≤1001Xp?,Yp?100

题解

咕咕咕·
毒瘤计算几何套FFT…谁想做啊…

  相关解决方案