比赛链接
这场简单(话说div4好少啊,打了二十多把了就两把div4)。D直接暴力就可以,E是暴力,F考察了树的性质,根据性质算数就行了,G是个状压。
A. Stair, Peak, or Neither?
题意:
给你三个数字 a a a 、 b b b 和 c c c 。请判断它们是形成一个阶梯、一个峰值,还是两者都不形成。
- 阶梯满足条件 a < b < c a\lt b\lt c a<b<c 。
- 峰值满足条件 a < b > c a\lt b\gt c a<b>c 。
思路:
签到
code:
#include <iostream>
#include <cstdio>
using namespace std;
int T,a,b,c;
int main(){
cin>>T;
while(T--){
cin>>a>>b>>c;
if(a<b && b<c)puts("STAIR");
else if(a<b && b>c)puts("PEAK");
else puts("NONE");
}
return 0;
}
B. Upscaling
题意:
给你一个整数 n n n 。输出一个由 2 × 2 2 \times 2 2×2 个方格组成的 2 n × 2 n 2n \times 2n 2n×2n 棋盘," # \texttt{\#} # “和” . \texttt{.} . “交替出现,左上角的方格为” # \texttt{\#} # "。
上图是 n = 1 , 2 , 3 , 4 n=1,2,3,4 n=1,2,3,4 的答案。
思路:
我们把四个格子看成一个格子的话,其实就非常简单,就是判断一下横纵坐标之和的奇偶性。
四个格子就把它压缩成一个格子即可(横纵坐标除以 2 2 2,由于是c++除法是向下取整的,因此这样四个格子除出来的坐标就变成了同一个坐标,也就实现了压缩在一起的功能)
code:
#include <iostream>
#include <cstdio>
using namespace std;
int T,n;
int main(){
cin>>T;
while(T--){
cin>>n;
n<<=1;
for(int i=0;i<n;i++,puts(""))
for(int j=0;j<n;j++)
putchar(((i/2+j/2)&1)?'.':'#');
}
return 0;
}
C. Clock Conversion
题意:
给出 24 小时制的时间,输出 12 小时制的相应时间。
- 24 时制 将一天分为从 00 00 00 到 23 23 23 的 24 个小时,其中从 00 00 00 到 59 59 59 的每个小时有 60 分钟。
- 12 时制 将一天分为两半:上半部分是 A M \mathrm{AM} AM ,下半部分是 P M \mathrm{PM} PM 。在每一半中,小时按 12 , 01 , 02 , 03 , … , 11 12, 01, 02, 03, \dots, 11 12,01,02,03,…,11 的顺序编号。每个小时有 60 60 60 分钟,从 00 00 00 到 59 59 59 依次编号。
思路:
牛客之前考过。
时间是 60 60 60 进制数,所以可以转化成同一单位可能会比较好做。判断是上午还是下午可以把小时和分钟都转化为分钟,然后判断。
十二时制的小时是 1 ∼ 12 1\sim12 1∼12 循环,可以用取模运算来实现。不过注意十二时制在 00 00 00 小时的时候要写 12 12 12 小时。
code:
#include <iostream>
#include <cstdio>
using namespace std;
int T,h,m;
int main(){
cin>>T;
while(T--){
scanf("%d:%d",&h,&m);
printf("%02d:%02d %s\n",(h+11)%12+1,m,((h*60+m>=720)?"PM":"AM"));
}
return 0;
}
D. Product of Binary Decimals
题意:
如果一个数是正整数,且其十进制符号中的所有数字都是 0 0 0 或 1 1 1 ,我们就称它为二进制十进制数。例如, 1 010 111 1\,010\,111 1010111 是二进制十进制,而 10 201 10\,201 10201 和 787 788 787\,788 787788 不是。
给定一个数 n n n ,问你是否可以把 n n n 表示为一些(不一定不同的)二进制十进制数的乘积。
思路:
因为 n ≤ 100000 n\le 100000 n≤100000,所以二进制十进制数总共只有 2 5 + 1 = 33 2^5+1=33 25+1=33 个。而它们 ≤ 100000 \le 100000 ≤100000 的乘积可以预想到也很少,所以我们直接暴力预处理出所有二进制十进制数以及它们的乘积即可。
code:
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=1e5+5;
int T,n;
bool vis[maxn];
void pb(int x){
for(int i=20;i>=0;i--)
cout<<((x>>i)&1);
cout<<endl;
}
vector<int> a;
void dfs(int pos,int x){
if(pos<1){
a.push_back(x);
return;
}
dfs(pos-1,x);
dfs(pos-1,x+pow(10,pos-1));
}
void init(){
dfs(5,0);
a.push_back(1e5);
for(auto x:a)vis[x]=true;
a.clear();
for(int i=1;i<=100000;i++){
if(vis[i]){
a.push_back(i);
for(auto x:a){
if(1ll*x*i<=1e5)vis[x*i]=true;
else break;
}
}
}
// for(int i=1;i<=1e5;i++)
// if(vis[i])
// cout<<i<<endl;
}
int main(){
init();
cin>>T;
while(T--){
cin>>n;
puts((vis[n])?"YES":"NO");
}
return 0;
}
E. Nearly Shortest Repeating Substring
题意:
给你一个长度为 n n n 的字符串 s s s ,它由小写拉丁字符组成。求最短字符串 k k k 的长度,使得数个(可能是一个) k k k 可以连接成一个长度与 s s s 相同的字符串,且最多只有一个不同的字符。
更具体地说,求最短字符串 k k k 的长度,使得 c = k + ⋯ + k ⏟ x times c = \underbrace{k + \cdots + k}_{x\rm\ \ \text{times}} c=x times k+⋯+k 对于某个正整数 x x x ,字符串 s s s 和 c c c 长度相同,而 c i ≠ s i c_i \neq s_i ci=si 中最多有一个 i i i 。(即存在 0 0 0 或 1 1 1 这样的位置)。
思路:
差点被单防,这题看着很难,实际上很简单。
因为 x x x 个串 k k k 接起来等于 s s s 的长度,所以 k k k 的长度一定是 s s s 长度的约数。假设不存在不同的字符,那么 k k k 相当于 s s s 的一个前缀,并且整个 s s s 相当于以 k k k 这个前缀循环 x x x 次。就算加入最多只有一个不同的字符的限制条件也不会产生太大改变。
考虑枚举所有可能的 k k k 的长度 ∣ k ∣ |k| ∣k∣,并对每个 ∣ k ∣ |k| ∣k∣ 进行验证。枚举 ∣ k ∣ |k| ∣k∣ 的时间复杂度是 O ( n ) O(\sqrt n) O(n) 的,验证就需要是 O ( n ) O(n) O(n) 的。如果不看 “一个不同的字符” 的限制条件,那么 k k k 相当于 s s s 的前缀, s s s 以 k k k 为单位进行循环,我们直接验证 s s s 以 ∣ k ∣ |k| ∣k∣ 为周期循环即可,加上限制条件后就是有一次将失配位置修改正确的机会。
code:
#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
using namespace std;
int T,n;
string s;
int main(){
cin>>T;
while(T--){
cin>>n>>s;
s=" "+s;
auto check=[&](int len)->bool{
string t=s;
bool f=false;
int idx;
for(int i=1;i<=n-len;i++){
if(t[i]!=t[i+len]){
if(!f){
map<char,int> cnt;
for(int j=(i-1)%len+1;j<=n;j+=len)cnt[t[j]]++;
if(!(cnt.size()==2 && min(cnt.begin()->second,cnt.rbegin()->second)==1))return false;
t[i]=t[i+len]=(cnt.begin()->second==1)?cnt.rbegin()->first:cnt.begin()->first;
f=true;
}
else return false;
}
}
return true;
};
bool flag=false;
for(int i=1;i<=n/2;i++){
if(n%i==0){
if(check(i)){
flag=true;
cout<<i<<endl;
break;
}
}
}
if(!flag)cout<<n<<endl;
}
return 0;
}
F. 0, 1, 2, Tree!
题意:
求一棵有 a + b + c a+b+c a+b+c 个顶点的有根树的最小高度:
- a a a 个顶点正好有 2 2 2 个子顶点、
- b b b 个顶点正好有 1 1 1 个子顶点、
- c c c 个顶点正好有 0 0 0 个子顶点。
如果不存在这样的树,则应报告 − 1 -1 −1。
上面的树以顶点为根,每个顶点都标有它的子顶点数。这里是 a = 2 a=2 a=2 、 b = 1 b=1 b=1 、 c = 3 c=3 c=3 ,高度是 2 2 2 。
† ^{\dagger} † 有根树是一个没有循环的连通图,它有一个特殊的顶点,叫做根。在有根树中,由边连接的任意两个顶点中,一个顶点是父顶点(离根较近的顶点),另一个顶点是子顶点。
树中两个顶点之间的距离就是它们之间最短路径的边数。有根树的高度是顶点到根的最大距离。
思路:
考察了二叉树的性质。
首先有个性质就是 a + 1 = c a+1=c a+1=c,可以这样理解:可以带两个儿子节点的节点需要消耗一个从上一层连过来边,得到两条向下的边,同理,带一个儿子节点的节点消耗一个从上一层连过来边,得到一条向下的边,叶子节点消耗一个从上一层连过来边,得到零条向下的边。一开始根节点可以看作上面初始有一条边,因此有多少增加出来的边,就有多少叶子节点与之对应。因此 a + 1 = c a+1=c a+1=c。当满足这个性质的时候,一定存在符合条件的树,本题有解。
而为了深度最浅,就要浅的深度放尽可能多的点,所以我们先依次放带两个儿子节点的节点,得到一个完全二叉树,然后按顺序向下接 带一个儿子节点的节点,最后再来一层叶子节点,就构造出了最浅的树。
引入两个满二叉树的性质:(假设根节点的深度为 0 0 0)
- 深度为 h h h 的满二叉树的节点个数为 2 h + 1 − 1 2^{h+1}-1 2h+1−1
- 第 h h h 深度上的节点个数为 2 h 2^h 2h
所以我们依次放入带两个儿子节点的节点后,假设它能摆到第 h h h 层上(可能没摆满),满足 2 h − 1 < a ≤ 2 h + 1 − 1 2^h-1\lt a\le 2^{h+1}-1 2h−1<a≤2h+1−1。这样第 h h h 层上放了 a 1 = a − ( 2 h − 1 ) a1=a-(2^h-1) a1=a−(2h−1) 个带两个儿子节点的节点,还剩 a 2 = 2 h − a 1 a2=2^h-a1 a2=2h−a1 个位置摆满。
因为没有带两个儿子节点的节点了,接下来的每层都只能容纳 2 ∗ a 1 + a 2 2*a1+a2 2∗a1+a2 个点。剩余的 b + c b+c b+c 个点最多可以再摆 ⌈ b + c − a 2 2 ∗ a 1 + a 2 ⌉ \left\lceil\dfrac{b+c-a2}{2*a1+a2}\right\rceil ⌈2∗a1+a2b+c−a2⌉ 层。
因此答案就是 h + ⌈ b + c − a 2 2 ∗ a 1 + a 2 ⌉ h+\left\lceil\dfrac{b+c-a2}{2*a1+a2}\right\rceil h+⌈2∗a1+a2b+c−a2⌉ 层。
code:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int T,a,b,c;
int main(){
cin>>T;
while(T--){
cin>>a>>b>>c;
if(a+1!=c){
puts("-1");
continue;
}
int h;
for(h=0;(1<<h+1)-1<a;h++);
int a1=a-(1<<h)+1,a2=(1<<h)-a1;
b+=c;
if(b>=a2)b-=a2;
else b-=b;
h+=(b+a1*2+a2-1)/(a1*2+a2);
cout<<h<<endl;
}
return 0;
}
G. Shuffling Songs
题意:
弗拉迪斯拉夫的播放列表由 n n n 首歌曲组成,编号从 1 1 1 到 n n n 。歌曲 i i i 的流派为 g i g_i gi ,作者为 w i w_i wi 。他希望在制作播放列表时,每对相邻的歌曲要么有相同的作者,要么有相同的流派(或两者皆有)。他把这样的播放列表称为令人兴奋的播放列表。 g i g_i gi 和 w i w_i wi 都是长度不超过 1 0 4 10^4 104 的字符串。
要制作一个令人兴奋的播放列表,不可能总是使用所有歌曲,因此洗牌过程分为两步。首先,删除一定数量(可能为零)的歌曲,然后重新排列播放列表中剩余的歌曲,使其令人兴奋。
由于弗拉迪斯拉夫不喜欢播放列表中的歌曲被移除,因此他希望制作的播放列表能尽可能少地移除歌曲。帮助他找出需要执行的最少移除次数,以便能够重新排列剩余的歌曲,使播放列表变得精彩。
思路:
把每首歌曲看作是图上的一个点。作者或者曲风相同就意味着两首歌曲可以放在相邻位置,我们给图上的对应两点连一条边。这样求一个合法的播放列表,其实就是求一个图上的路径。或者说,是求 “哈密尔顿路径”。
因为给的歌曲数量很少,只有 16 16 16 个,因此我们可以用一个二进制数存储我们路径上经过的所有点。用 d p [ u ] [ s t ] dp[u][st] dp[u][st] 表示能否得到一个经过的所有点为 s t st st,且路径上最后一个点为 u u u 的 路径。这样推出所有可能的路径包含点的情况,并从中取出包含点最多的路径即可。
code:
写的记忆化搜索。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=20;
int T,n;
string song[maxn][2];
int head[maxn],counter;
struct edge{
int v,nxt;
}e[maxn*maxn];
void add(int u,int v){
e[++counter].v=v;
e[counter].nxt=head[u];
head[u]=counter;
}
bool dp[maxn][1<<maxn];
int ans=0;
void pd(int x){
for(int i=20;i>=0;i--)
cout<<((x>>i)&1);
cout<<endl;
}
int count(int st){
int cnt=0;
for(int i=st;i;i^=i&-i)cnt++;
return cnt;
}
void dfs(int u,int st){
// cout<<u<<" ";pd(st);
dp[u][st]=true;
ans=max(ans,count(st));
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].v;
if((st>>(v-1))&1 || dp[v][st|(1<<v-1)])continue;
dfs(v,st|(1<<v-1));
}
}
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++)cin>>song[i][0]>>song[i][1];
counter=0;
for(int i=1;i<=n;i++)head[i]=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
if(song[i][0]==song[j][0] || song[i][1]==song[j][1]){
add(i,j);
add(j,i);
// printf("edge:%d-%d\n",i,j);
}
}
for(int i=1;i<=n;i++)
for(int st=0;st<(1<<n+1);st++)
dp[i][st]=false;
ans=0;
for(int u=1;u<=n;u++)dfs(u,1<<u-1);
cout<<n-ans<<endl;
}
return 0;
}
队友的写法,没有去建图,而是hash一下。并且推状态的时候是递推,不是搜索。
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
#define rep(i, x, y) for(int i=(x), _=(y);i<=_;i++)
#define rrep(i, x, y) for(int i=(x), _=(y);i>=_;i--)
#define all(x) x.begin(),x.end()
#define PII pair<int, int>
#define x first
#define y second
#define ll long long
#define int long long
#define endl '\n'
using i64 = long long;
const int N = 20;
int g[N];
int w[N];
int dp[N][1 << N];
void solve() {
int n; cin >> n;
map<string, int> mp;
int tot = 0;
for (int i = 0; i < n; i++) {
string a, b; cin >> a >> b;
if (mp.count(a)) g[i] = mp[a];
else {
g[i] = ++tot;
mp[a] = g[i];
}
if (mp.count(b)) w[i] = mp[b];
else {
w[i] = ++tot;
mp[b] = w[i];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < (1 << n); j++) {
dp[i][j] = 0;
}
dp[i][1 << i] = 1;
}
int ans = 1;
for (int i = 0; i < (1 << n); i++) {
for (int j = 0; j < n; j++) {
if (i >> j & 1) {
int t = i ^ (1 << j);
for (int k = 0; k < n; k++) {
if (t >> k & 1) {
if (g[j] == g[k] || w[j] == w[k])
dp[j][i] = max(dp[j][i], dp[k][t] + 1);
ans = max(ans, dp[j][i]);
}
}
}
}
}
cout << n - ans << '\n';
}
signed main() {
IOS;
int T; cin >> T;
while (T--)
solve();
return 0;
}