Mirko喜欢打字,由于他上课时经常感到无聊,所以他的老师分配给他一项任务。
Mirko必须重打一本包含 N N N个句子的书,句子之间用空格分隔。在这本书中,每个句子由一个或多个用空格分隔的单词组成,其中只有最后一个单词的最后一个字符是标点符号(.
,?
或者!
)。其余单词不包含标点符号。
单词是一个字符数组,可能包含小写或大写的英文字母、数字或标点符号,其中标点符号只会出现在句子最后一个单词的末尾。
虽然Mirko喜欢打字,但他不喜欢打名字。名字是指除第一个字符是大写英文字母之外其他字符都是小写英文字母的单词,但最后一个字符除外,名字允许标点符号作为单词的最后一个字符。
在他决定开始重打之前,Mirko想知道书中每一句话有多少个名字。写一个程序来帮助他!
第一行输入包含整数 N N N( 1 ≤ N ≤ 5 1≤N≤5 1≤N≤5)。表示句子的数量。
第二行输入包含书中的 N N N个句子。
保证书中的总字符不超过 1000 1000 1000。
输出 N N N行每行一个整数。第i行的整数表示第i句话中有多少个名字。
对于40%的数据, N = 1 N=1 N=1。
奇怪的字符串处理题。
最好的方法是一个一个字符串输入依次判断,如果串中有标点就输出。
然而蒟蒻脑袋短路,于是成了一个一个字符输入…差点被Linux的两个换行断送了80
分…
注意单独的大写字母也算作名字。
复杂度: O ( L ) O(L) O(L)
#include
#include
#include
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);
}
}
Greedy得到了一块棋盘作为生日礼物。棋盘有 N N N行 M M M列,每个格子中都有一个小写英文字母。在他的生日聚会上,每个人都很无聊,所以他们决定用棋盘玩一个简单的游戏。
首先在左上角标有坐标 ( 1 , 1 ) (1,1) (1,1)的格子放置一个棋子。在每一个回合中,我们必须将芯片向右或向下移动一格,前提是没有移出棋盘。游戏结束时,将棋子移动到标有坐标 ( N , M ) (N,M) (N,M)的格子即棋盘的右下角。在游戏中,我们依次记下移动棋子时经过的格子里的字母,从而构造一个单词。游戏的目标是找到能构造出的字典序最小的单词。
那些成功地构造了字典序最小单词的玩家会得到一袋糖果作为奖励。Greedy愿意付出任何代价来赢得糖果,所以他要求你写一个程序,可以找到能构造出的字典序最小的单词。
第一行输入包含整数 N N N和 M M M( 1 ≤ N , M ≤ 2000 1≤N,M≤2000 1≤N,M≤2000)。表示场地的行数和列数。
接下来 N N N行每行包含一个字符串,每个字符串由 M M M个小写英文字母组成。表示棋盘。
输出一个字符串。表示能构造出的字典序最小的单词。
对于50%的数据,每个格子下方和右方的格子中的字母是不同的。
首先考虑50%,发现可选的路径只有一条,只需要每次选字典序更小的字母往下走就可以了。
现在考虑下方和右方的字母相同的情况,这时候就有两条路可以走了。
这种路径不断拓展情况很像BFS。
由于第 k k k步合法的点一定满足 n + m − 2 = k n+m-2=k n+m−2=k,可以直接枚举,因此不需要BFS。
每次我们将走到第 k k k步合法的点拓展一下,将拓展的所有点做下标记,然后扫一遍拓展的点,将所表示的字母不满足最小字典序的点标记删除即可。
具体请见代码。
复杂度: O ( n m ) O(nm) O(nm)
#include
#include
#include
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;
}
Mislav有 N N N个无限体积的杯子,每一个杯子中都有一些水。Mislav想喝掉所有的水,但他不想喝超过K杯水。Mistrav能做的就是将一个杯子中的水倒入另一个杯子中。
不幸的是,挑选哪两个杯子进行倒水操作对Mislav来说很重要,因为并非所有的杯子都离他一样远。更准确地说,从i号杯子向j号杯子倒水所付出的代价为 C i , j C_{i,j} Ci,j。
帮助Mislav找到他需要付出的总代价的最小值。
第一行输入包含整数 N N N和 K K K( 1 ≤ K ≤ N ≤ 20 1≤K≤N≤20 1≤K≤N≤20)。表示水杯的总数和Mislav最多能喝多少杯。
接下来N行每行包含 N N N个整数 C i , j C_{i,j} Ci,j( 0 ≤ C i , j ≤ 1 0 5 0≤C_{i,j}≤10^5 0≤Ci,j≤105)。第 i + 1 i+1 i+1行的第 j j j个整数表示从第 i i i个杯子第 j j j个杯子倒水所需要付出的代价。保证 C i , i C_{i,i} Ci,i等于0。
输出一个整数。表示Mislav需要付出的总代价的最小值。
对于40%的数据, N ≤ 10 N≤10 N≤10。
状压dp模版题。
看到 n ≤ 20 n≤20 n≤20首先想到状压或者搜索。
考虑到一杯水倒入其它杯中后就不会再有水倒入,显然这是一个0/1
状态。
因此设 d p [ S ] dp[S] dp[S]为状态为 S S S耗费的最小步数。
枚举两个非空的水杯暴力转移即可。
转移方程: d p [ S + ( 1 < < i ) ] = m i n { d p [ S ] + c [ i ] [ j ] } dp[S+(1<<i)]=min\{dp[S]+c[i][j]\} dp[S+(1<<i)]=min{dp[S]+c[i][j]}
复杂度: O ( n 2 ∗ 2 n ) O(n^2*2^n) O(n2∗2n)
#include
#include
#include
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);
}
质量算数表达式由乘号,括号,数字和加号组成。
质量算数表达式由以下方式定义:
已知一个质量表达式,其中的数字用问号替换。
求这个表达式可能具有的最大值。
简单模拟题。
首先我们必须要理解题目的意思(由于作者太懒请自行理解…)。
然后很容易发现,对于每个质量表达式,我们就是要在满足 ∑ i = 1 k A i ≤ Z k \sum^{k}_{i=1} A_i≤Z_k ∑i=1kAi≤Zk, A i A_i Ai小于等于 A i A_i Ai表达式的最大值的条件下,找到当前表达式的最大值。
设 M i M_i Mi表示 A i A_i Ai表达式的最大值,那么可以分为以下三种情况。
显然我们可以先处理表达式深的最大值,由深到浅依次处理,而这个可以直接用递归实现。
复杂度: O ( n ) O(n) O(n)
#include
#include
#include
#include
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));
}
Marton的朋友Cero有一个包含 N N N个正整数的数组。开始时,Cero在黑板上写上第一个数字,然后,他将第二个数字写在第一个数字的左边或右边,之后,他将第三个数字写在目前为止写下的所有数字的左边或右边,以此类推。当他写下全部 N N N个数字后,会形成一个新的数组。
●Marton想知道新数组的最长严格递增子序列的长度。
●Marton还想知道这种最长严格递增子序列的数量。
更确切的说,如果所有能构建出的新数组中最长严格递增子序列的最长长度为 M M M,则想Marton知道所有可以构建的每个新数组中长度为 M M M的最长严格递增子序列的数目的总和。如果新数组使用不同的顺序构建,则称为不同的新数组。对于同一个新数组,如果两个最长严格递增子序列在至少一个位置上不同,则称为两个不同的最长严格递增子序列。
考虑到这样的子序列的数目非常大,只需求出答案对 1 0 9 + 7 10^9+7 109+7取模的结果。
Cero要求你来回答Marton的两个问题。
第一行输入包含整数 N N N( 1 ≤ N ≤ 2 ∗ 1 0 5 1≤N≤2*10^5 1≤N≤2∗105)。表示数组中有多少个数。
第二行输入包含 N N N个正整数 A i A_i Ai( 1 ≤ A i ≤ 1 0 9 1≤A_i≤10^9 1≤Ai≤109)。表示Cero原本的数组。
输出两个整数,中间用空格分隔。分别表示最长严格递增子序列的长度和这种长度的最长严格递增子序列的的数目。
对于30%的数据, N ≤ 20 N≤20 N≤20。
对于50%的数据, N ≤ 1000 N≤1000 N≤1000。
毒瘤dp题。
先考虑解决第一个问题。
明显是一个dp可以解决的问题。考虑到原问题就是求一些数字反转到第一个数前面后求最长上升子序列,而反转过来的数字的最长上升子序列就是原数列选中数字的最长下降子序列,那么问题就转化为了求一个最长上升子序列和最长下降子序列且最长下降子序列中最大的数小于最长上升子序列中最小的数。
考虑如何满足这个条件,显然我们只需要控制最长上升子序列和最长下降子序列的左端点重合就可以了,由于数列是严格上升,可以证明没有重复元素的存在。
设 d p 1 [ i ] , d p 2 [ i ] dp1[i],dp2[i] dp1[i],dp2[i]分别为左端点为 i i i的最长上升子序列和最长下降子序列的最大长度, c n t 1 [ i ] , c n t 2 [ i ] cnt1[i],cnt2[i] cnt1[i],cnt2[i]分别为左端点为 i i i的最长上升子序列和最长下降子序列的最大长度条件下的子序列个数。
首先这个序列的长度显然为 d p 1 [ i ] + d p 2 [ i ] − 1 dp1[i]+dp2[i]-1 dp1[i]+dp2[i]−1,那么我们就能很快找到序列的最长长度 L e n Len Len。
对于每个满足 d p 1 [ i ] + d p 2 [ i ] − 1 = L e n dp1[i]+dp2[i]-1=Len dp1[i]+dp2[i]−1=Len的位置,我们需要计算它对方案数的贡献。首先这里合法的串显然有 c n t 1 [ i ] ∗ c n t 2 [ i ] cnt1[i]*cnt2[i] cnt1[i]∗cnt2[i]种,然后我们需要考虑其它数如何摆放。
这里必须要纠正题解的一处明显的错误,方案数并不是因为其它 n − L e n n-Len n−Len个数都有两种选择而使得方案要乘上 2 n − L e n 2^{n-Len} 2n−Len,因为如果选择的串中没有 a 1 a_1 a1的话,那么 a 1 a_1 a1其实只有一种选择。实质上,它需要分类讨论:
1
,此时, i i i就有在 a 1 a_1 a1前面或者后面里两种选择,而串外除去 a 1 a_1 a1的所有点都可以放在串的前面和后面,因此有两种选择的点的个数为 n − L e n n-Len n−Len,方案数为 2 n − L e n 2^{n-Len} 2n−Len。1
,此时, i i i由于是第一个放下的点,因此只有一种选择,而选中的串外的所有点都可以放在串的前面和后面,因此有两种选择的点的个数为 n − L e n n-Len n−Len,方案数为 2 n − L e n 2^{n-Len} 2n−Len。综上,一个位置对答案的贡献为 c n t 1 [ i ] ∗ c n t 2 [ i ] ∗ 2 n − L e n cnt1[i]*cnt2[i]*2^{n-Len} cnt1[i]∗cnt2[i]∗2n−Len。
然而直接计算最长上升子序列和最长下降子序列及其个数是 O ( n 2 ) O(n^2) O(n2)的,显然我们需要一个 O ( n log n ) O(n\log n) O(nlogn)的解法。考虑将 A i A_i Ai离散化,用树状数组(Fenwick tree)维护 d p , c n t dp,cnt dp,cnt两个值,在维护 d p dp dp前缀最大值时同时维护方案数即可。
复杂度: O ( n log n ) O(n\log n) O(nlogn)
#include
#include
#include
#include
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);
}
你知道酒店和汽车旅馆之间有什么区别吗?没错,差别在于生活在那里的苍蝇数量。Norman是美国最受欢迎的汽车旅馆之一的所有者,但他的母亲坚持把它变成一家酒店。这就是为什么Norman在2016年的圣诞礼物是一个 K K K边形的苍蝇拍(一个杀戮装置)。
为了满足母亲的要求,Norman被迫站在一个有 N N N个苍蝇的窗户前。由于Norman不想伤害苍蝇,他想知道有多少方式,他用苍蝇拍击打窗户,而不伤害一只苍蝇。
窗户是一个矩形,左下顶点的坐标是 ( 0 , 0 ) (0,0) (0,0)。Norman击打窗户时,苍蝇拍的每个顶点都必须位于整数坐标上,飞行苍蝇拍必须位于窗户内的区域,如果苍蝇位于苍蝇拍的顶点,边或多边形内部,则苍蝇视为被伤害。
第一行输入包含三个整数 X p X_p Xp, Y p Y_p Yp( 1 ≤ X p , Y p ≤ 500 1≤X_p,Y_p≤500 1≤Xp,Yp≤500)和 N N N( 0 ≤ N ≤ X p ∗ Y p 0≤N≤X_p*Y_p 0≤N≤Xp∗Yp)。前两个整数表示窗户右上角的坐标,第三个整数表示苍蝇的数量。
接下来 N N N行中的每一行包含两个整数 x x x( 0 < x < X p 0<x<X_p 0<x<Xp)和 y y y( 0 < y < Y p 0<y<Y_p 0<y<Yp)。表示苍蝇的坐标。
接下来一行包含了一个整数 K K K( 3 ≤ K ≤ 10000 3≤K≤10000 3≤K≤10000)。表示多边形的边数。
接下来 K K K行中的每一行包含两个整数 A A A和 B B B( − 1 0 9 ≤ A , B ≤ 1 0 9 -10^9≤A,B≤10^9 −109≤A,B≤109)。
表示飞行苍蝇拍各个顶点的坐标。按输入依次连接 K K K个点,得到的 K K K边形就是苍蝇拍的形状。
输出一个整数。表示不伤害苍蝇的情况下Norman有多少种用苍蝇击打窗户的方法。
对于62.5%的数据, 1 ≤ X p , Y p ≤ 100 1≤X_p,Y_p≤100 1≤Xp,Yp≤100。
咕咕咕·
毒瘤计算几何套FFT…谁想做啊…