小蓝要和朋友合作开发一个时间显示的网站。
在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从 1970 年 1 月 1 日 00:00:00 到当前时刻经过的毫秒数。
现在,小蓝要在客户端显示出这个时间。
小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
输入格式
输入一行包含一个整数,表示时间。
输出格式
输出时分秒表示的当前时间,格式形如 HH:MM:SS,其中 HH 表示时,值为 0 到 23,MM 表示分,值为 0 到 59,SS 表示秒,值为 0 到 59。
时、分、秒不足两位时补前导 0。
数据范围
对于所有评测用例,给定的时间为不超过 1018 的正整数。
输入样例1:
46800999
输出样例1:
13:00:00
输入样例2:
1618708103123
输出样例2:
01:08:23
1
秒=1000
毫秒,1
分钟=60
秒,1
小时=60
分钟,所以1
毫秒=1/1000
秒1/60000
分钟=1/3600000
小时,直接换算即可,但要求h取值(0\~23)
,m取值(0\~59)
,s取值(0~59)
,用取余即可
#include
#include
using namespace std;
typedef long long LL;
LL n;
int main()
{
cin>>n;
LL h=n/1000/3600;//得到小时
LL m=n/1000/60;//得到分钟
LL s=n/1000;//得到秒
printf("%02lld:%02lld:%02lld",h%24,m%60,s%60);
}
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN。
请你计算一共可以称出多少种不同的正整数重量?
注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN。
输出格式
输出一个整数代表答案。
数据范围
对于 50% 的评测用例,1≤N≤15。
对于所有评测用例,1≤N≤100,N 个砝码总重不超过 105。
输入样例:
3
1 4 6
输出样例:
10
样例解释
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。
题型:背包,
dp
闫氏dp
:
1.状态表示f[i][j]
集合
:表示从前i个砝码选,总质量为j的方案数
属性
:是否为空
2.状态计算
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][j-abs(w[i])
解释一下:对于每一个砝码,都有三种选择,选或者不选,对于第i
个物品,如果我们不选,那么根据集合的表示可知,从前i个物品选总质量为j
,所以不选的时候要保证质量为j
,则就是f[i-1][j]
,就是从前i-1
个砝码选,总质量为j
,同理,如果减去
第i
个砝码的总质量为j
,那么前i-1
个砝码选择的质量就是j+w[i]
,即f[i-1][j+w[i]]
,如果加上
第i
个砝码的质量的总质量为j
,那么从前i-1
个砝码中选的总质量就为j-w[i]
,即f[i-1][j-w[i]]
,由实际含义可以,f[i][j]=f[i][-j]
因为可以通过镜像的操作而得到,故而f[i][j-w]=f[i][w-j]=f[i][abs(j-w)]
。就避免了数组下标为负数的情况。而这三种情况只要有一种能表示质量j
,就说明能称出质量为j
的,那么就有不为空,所以三种情况用或运算即可。在这道题,最大能表示的重量就是所有给的砝码的重量之和,最小就是0
,所以f[0][0]
初始化为true
。所以最后的答案就是f[n][i]
,其中i
从0
到最大重量,即从前n
件物品中选,总体积为i
的方案数。
#include
#include
using namespace std;
const int N=110,M=200010;
int w[N];
bool f[N][M];
int n,sum;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&w[i]),sum+=w[i];
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=sum;j++){
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];
}
}
int res=0;
for(int i=1;i<=sum;i++){
if(f[n][i]) res++;
}
printf("%d\n",res);
return 0;
}
下面的图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, …
给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?
输入格式
输入一个整数 N。
输出格式
输出一个整数代表答案。
数据范围
对于 20% 的评测用例,1≤N≤10;
对于所有评测用例,1≤N≤109。
输入样例:
6
输出样例:
13
杨辉三角的特点:
左右对称
,而且中间的数最大
,如果找第一次出现的n,一定在左边,所以只看左半边
就行
通过这个图形可以看出,斜着看每一行,越往下越大,横着看每一行,越往右越大,即中间的数会比两边大,中间的数满足规律,也就是C(2n,n)
,而且斜着和横着都是单调递增的,所以可以想到用二分
用k
来枚举斜行
,从哪里开始枚举?我们要求的是等于k的最小的数,所以我们应该从最大的开始枚举,也就是C(2k,k)
,因为**假设最右边的数就是要找到n,它一定是最小的,因为它左边的数都比它小,不满足等于n,而它右边和下边的数,就算等于n,也不会比它先出现。**所以二分的时候,我们就枚举k。
二分的左右边界,上限,一定是2*k
,因为它是最大的,下限,一定是n
,因为这个题一定有解,即C(n,1)=n
k的取值,本题k去16
,因为我们从C(2k,k)
开始枚举,N
的最大值是10^9
,而C(32,16)<10\^9
,C(34,17)>10^9
计算求出来的C(r,k)是第几个数,第0
行一个数,第一行1
个数,第二行3
个…第r
行r+1
,等差数列求和r*(r+1)/2
,第k
斜行是第k+1
个数,所以该位置是r*(r+1)/2+k+1
#include
#include
#include
using namespace std;
typedef long long LL;
int n;
LL C(int a,int b){//求组合数
LL res=1;
for(int i=a,j=1;j<=b;j++,i--){
res=res*i/j;
if(res>n) return res;//大于n就没必要再求了 防止爆LL
}
return res;
}
bool check(int k){//检查C(,k),k表示斜行,我们枚举的是斜行
int l=2*k,r=max(n,l);//因为最大的数是最靠近中间的,也就是C(2*k,k),所以上阶是2*k,该题一定有解,因为C(n,1)=n,所以下界是n
while(l<r){
int mid=l+r>>1;
if(C(mid,k)>=n) r=mid;//因为枚举斜行,行数越小,组合数越小,当我们枚举的数太大,就说明在上面的斜行
else l=mid+1;
}
if(C(r,k)!=n) return false;
cout<<1LL*r*(r+1)/2+k+1<<endl;//第0行一个数,第一行1个数,第二行3个..第r行r+1,等差数列求和r*(r+1)/2,第k斜行是第k+1个数,所以该位置是r*(r+1)/2+k+1
return true;
}
int main()
{
cin>>n;
for(int k=16;;k--)//从斜行最大的16开始枚举
if(check(k)) break;
return 0;
}
给定序列 (a1,a2,⋅⋅⋅,an)=(1,2,⋅⋅⋅,n),即 ai=i。
小蓝将对这个序列进行 m 次操作,每次可能是将 a1,a2,⋅⋅⋅,aqi 降序排列,或者将 aqi,aqi+1,⋅⋅⋅,an 升序排列。
请求出操作完成后的序列。
输入格式
输入的第一行包含两个整数 n,m,分别表示序列的长度和操作次数。
接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 pi,qi 表示操作类型和参数。当 pi=0 时,表示将 a1,a2,⋅⋅⋅,aqi 降序排列;当 pi=1 时,表示将 aqi,aqi+1,⋅⋅⋅,an 升序排列。
输出格式
输出一行,包含 n 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
数据范围
对于 30% 的评测用例,n,m≤1000;
对于 60% 的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤105,0≤pi≤1,1≤qi≤n。
输入样例:
3 3
0 3
1 2
0 2
输出样例:
3 1 2
样例解释
原数列为 (1,2,3)。第 1 步后为 (3,2,1)。
第 2 步后为 (3,1,2)。
第 3 步后为 (3,1,2)。与第 2 步操作后相同,因为前两个数已经是降序了。
找规律
第一种情况:
第二种情况:
第三种情况:
第四种情况:
所以,我们只需要看是连续前缀还是连续后缀,又或者是前缀混合后缀且第三段比前两段短,又或者是前缀混合后缀且第三段比前两段长,但每一次都只需要考虑端点,根据情况取最大(前缀)或者最小(后缀)端点。
#include
#include
#include
using namespace std;
#define x first
#define y second
typedef pair<int,int> PII;
const int N=100010;
int n,m;
PII stk[N];//第一个参数为0或者1表示进行前缀操作还是后缀操作
int res[N];
int main()
{
scanf("%d%d",&n,&m);
int top=0;
while(m--){
int p,q;
scanf("%d%d",&p,&q);
if(!p){//进行前缀操作
while(top&&stk[top].x==0) q=max(q,stk[top--].y);//如果是连续的前缀操作,只需要保留最长的前缀
while(top>=2&&stk[top-1].y<=q) top-=2;//如果有一段前缀一段后缀连续,且比当前的前缀操作短,可直接删去两段
stk[++top]={0,q};
}
else if(top){//当栈不空再进行后缀,因为后缀是正序,如果第一个操作是后缀操作没有意义
while(top&&stk[top].x==1) q=min(q,stk[top--].y);
while(top>=2&&stk[top-1].y>=q) top-=2;
stk[++top]={1,q};
}
}
int k=n,l=1,r=n;//k用来表示要填的数1~n,l,r是枚举的左右端点,根据栈里的操作填数
for(int i=1;i<=top;i++){
if(stk[i].x==0)
while(stk[i].y<r&&l<=r) res[r--]=k--;//如果是前缀,就是将前半段逆序,但是后半段不变,可直接填数
else
while(stk[i].y>l&&l<=r) res[l++]=k--;//如果是后缀,就是将后半段正序,但是前半段不变,可直接填数
if(l>r) break;
}
if(top%2)// 若l < r, 表示中间还有些数没有填上,操作次数为奇数,则下一次操作为前缀操作
while(l<=r) res[l++]=k--;
else
while(l<=r) res[r--]=k--;
for(int i=1;i<=n;i++) printf("%d ",res[i]);
return 0;
}
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
输入格式
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。
输出格式
输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007 (即 109+7) 的余数。
数据范围
对于 40% 的评测用例,|s|≤200。
对于所有评测用例,1≤|s|≤5000。
输入样例:
((()
输出样例:
5
左括号的添加和右括号的添加是相互独立的,可以分开来算,证明如下
所以先计算左括号有多少种添加方案,再计算右括号有多少种添加方案,两个结果相乘就行
如何保证计算左括号的添加方案时不会重复
,可以以所有右括号
为分割
,将每一个部分看成一段,然后我们计算时规定,只在右括号前面添加左括号,所以枚举的时候,遇到左括号前面不用添加任何东西,遇到右括号前面可以添加任意多个左括号
然后可以解题了,用闫氏dp
状态表示f[i][j]
集合
:表示只考虑前i个括号,左括号比右括号多j个元素的集合
属性
:数量
状态计算
:1. 如果当前字符是左括号,不用添加f[i][j]=f[i-1][j-1]
2. 如果当前字符是右括号,f[i][j]=f[i-1][j+1]+f[i][j-1]
对于状态计算1
来说,如果当前字符是左括号,不用添加,所以根据集合描述可知,考虑前i
个括号,左括号比右括号多j个,那么考虑前i-1
个,减去第i
个左括号,就多1
个左括号,所以f[i][j]=f[i-1][j-1]
对于状态计算2
,如果当前字符是右括号,我们可以添加0
个(不添加,f[i-][j+1]
,因为考虑前i个,左比右多j
,那么去掉第i个右,前i-1
,左比右多j+1
个),添加1个f[i-1][j]
…添加j+1
个左括号(f[i-1][0]
,因为前i
个括号,左比右多j
,除掉添加的j+1
个,原来左比右多0个)f[i][j]=f[i-1][j+1]+f[i-1][j]+...+f[i-1][0]
,经过下面的换算:
红色部分替换,最后得到f[i][j]=f[i-1][j+1]+f[i][j-1]
最后的结果是遍历f[n][j]
,j
从0
到n
,找到第一个值不为零的数就是答案,因为题目中说要求尽可能少地添加若干括号使得括号序列变得合法,所以当j
越小,添加的括号越小。
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N=5010,mod=1e9+7;
char str[N];
LL f[N][N];
int n;
int cal()
{
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1;i<=n;i++){
if(str[i]=='('){//如果是左括号,无须添加
for(int j=1;j<=n;j++)
f[i][j]=f[i-1][j-1];
}else{//如果是右括号
f[i][0]=(f[i-1][0]+f[i-1][1])%mod;//为了防止j-1得到-1越界,特别处理一下
for(int j=1;j<=n;j++){
f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
}
}
}
for(int i=0;i<=n;i++)
if(f[n][i]) return f[n][i];
return -1;
}
int main(){
scanf("%s",str+1);
n=strlen(str+1);
LL l=cal();
reverse(str+1,str+n+1);
for(int i=1;i<=n;i++){
if(str[i]=='(') str[i]=')';
else str[i]='(';
}
LL r=cal();
printf("%lld",l*r%mod);
return 0;
}
Alice 和 Bob 正在玩一个异或数列的游戏。
初始时,Alice 和 Bob 分别有一个整数 a 和 b(初始时,a,b 均为 0),有一个给定的长度为 n 的公共数列 X1,X2,⋅⋅⋅,Xn。
Alice 和 Bob 轮流操作,Alice 先手,每步可以在以下两种选项中选一种:
选项 1:从数列中选一个 Xi 给 Alice 的数异或上,或者说令 a 变为 a⊕Xi。(其中 ⊕ 表示按位异或)
选项 2:从数列中选一个 Xi 给 Bob 的数异或上,或者说令 b 变为 b⊕Xi。
每个数 Xi 都只能用一次,当所有 Xi 均被使用后(n 轮后)游戏结束。
游戏结束时,拥有的数比较大的一方获胜,如果双方数值相同,即为平手。
现在双方都足够聪明,都采用最优策略,请问谁能获胜?
输入格式
每个评测用例包含多组询问。询问之间彼此独立。
输入的第一行包含一个整数 T,表示询问数。
接下来 T 行每行包含一组询问。其中第 i 行的第一个整数 ni 表示数列长度,随后 ni 个整数 X1,X2,⋅⋅⋅,Xni 表示数列中的每个数。
输出格式
输出 T 行,依次对应每组询问的答案。
每行包含一个整数 1、0 或 −1 分别表示 Alice 胜、平局或败。
数据范围
对于所有评测用例,1≤T≤2×105,1≤∑i=1Tni≤2×105,0≤Xi<220。
输入样例:
4
1 1
1 0
2 2 1
7 992438 1006399 781139 985280 4729 872779 563580
输出样例:
1
0
1
1
初始时A和B都为0, 由异或的性质最终
A⨁B=X1⨁X2⨁Xi...
如果所有X异或的结果为0,那么说明最终的A 和 B 是相同的,直接输出平局0。
由于异或中:与 1 异或是取反,与 0 异或不变
,
当某一位的num[i]为偶数,则游戏结果的这一位一定相等,直接看下一位出现的次数
如果num[i]的个数为1,则一定是先手赢(抢了1之后,就没有1了|只有A最高位变成了1)
那么当num[i]为大于1的奇数, 0的个数为偶数, 则先手赢
当num[i]为大于1的奇数, 0的个数为奇数,则后手赢
#include
#include
#include
using namespace std;
const int N=22;
int num[N];
int get_num(int x){
int cnt=1;
while(x){
if(x&1) num[cnt]++;
cnt++;
x>>=1;
}
}
int main()
{
int n;
cin>>n;
while(n--){
memset(num,0,sizeof num);
int x,t,sum=0;
scanf("%d",&t);
for(int i=1;i<=t;i++){
scanf("%d",&x);
get_num(x);
sum^=x;
}
if(!sum) puts("0");
else{
for(int i=20;i>=1;i--){
if(num[i]==1){
puts("1");
break;
}else if(num[i]&1){
if(t%2==0) puts("-1");
else puts("1");
break;
}
}
}
}
}
对于一棵多叉树,我们可以通过 “左孩子右兄弟” 表示法,将其转化成一棵二叉树。
如果我们认为每个结点的子结点是无序的,那么得到的二叉树可能不唯一。
换句话说,每个结点可以选任意子结点作为左孩子,并按任意顺序连接右兄弟。
给定一棵包含 N 个结点的多叉树,结点从 1 至 N 编号,其中 1 号结点是根,每个结点的父结点的编号比自己的编号小。
请你计算其通过 “左孩子右兄弟” 表示法转化成的二叉树,高度最高是多少。
注:只有根结点这一个结点的树高度为 0。
例如如下的多叉树:
可能有以下 3 种 (这里只列出 3 种,并不是全部) 不同的 “左孩子右兄弟”表示:
其中最后一种高度最高,为 4。
输入格式
输入的第一行包含一个整数 N。
以下 N−1 行,每行包含一个整数,依次表示 2 至 N 号结点的父结点编号。
输出格式
输出一个整数表示答案。
数据范围
对于 30% 的评测用例,1≤N≤20;
对于所有评测用例,1≤N≤105。
输入样例:
5
1
1
1
2
输出样例:
4
二叉树转化成树:连接所有兄弟节点,只保留最右边的那条与父母的连线,其他兄弟和父母的连线删除,每次这样做之后,如果想要得到树的最大高度,那么应该保留一个孩子成为右节点,其他结点成为保留的兄弟的孩子,那么最大高度就是根节点孩子的数目加上,以孩子结点为根的子树的最大高度,所以如果用
f[u]
表示以u
为根的子树的最大高度,那么f[u]=max(f[u],f[j]+num[u])
,其中j为u的孩子,num[u]
表示以u
为根的孩子的数目
#include
#include
#include
using namespace std;
const int N=100010;
int h[N],e[N],ne[N],idx;
int n;
int num[N],f[N];
void add(int a,int b){//邻接表
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){//递归搜索
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
dfs(j);//先求所有的孩子树的高度 再回溯求根的
f[u]=max(f[u],f[j]+num[u]);//以u为根节点的树的最大高度等于,以u为根的孩子的个数加上某个子树的最大高度
}
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
for(int i=2;i<=n;i++){
int x;
scanf("%d",&x);
add(x,i);
num[x]++;
}
dfs(1);
printf("%d\n",f[1]);
return 0;
}
你有一架天平。现在你要设计一套砝码,使得利用这些砝码可以称出任意小于等于 N 的正整数重量。
那么这套砝码最少需要包含多少个砝码?
注意砝码可以放在天平两边。
输入格式
输入包含一个正整数 N。
输出格式
输出一个整数代表答案。
数据范围
对于所有评测用例,1≤N≤109。
输入样例:
7
输出样例:
3
样例解释
3 个砝码重量是 1、4、6,可以称出 1 至 7 的所有重量。1 = 1;
2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
少于 3 个砝码不可能称出 1 至 7 的所有重量。
大家可以参考这篇博客
#include
#include
using namespace std;
int n;
int main()
{
cin>>n;
int cnt=1,weight=1,sum=1;
while(1){
if(sum>=n) break;
weight*=3;
sum+=weight;
cnt++;
}
printf("%d\n",cnt);
return 0;
}