A.移动杠铃
得分:0分
预期得分:100分
题目大意
- 两排数,你的目标是将数值相同的放到一起。
- 滚动不消耗代价。
- 提起消耗的代价为数值。
- 你需要最小化提起的数值限制,也就是移动的最大值尽可能小
30 30 分算法:
一旦一个数值使用了第二种操作,可以直接把它看成消失了,因为可以不对剩下的数造成任何阻碍。
枚举每个数是否使用第二种操作,然后 check c h e c k 一下剩下的数对中间是否都没有障碍数。
时间复杂度 O(a2n) O ( a 2 n ) ,期望得分 30 30 。
但是你只要想出了第一个方法,你一般也就不会只有30分,但这都是后话。
60 60 ~ 80 80 分算法:
考虑一对数,如果它们跨排,那么一定会使用第二种操作。
如果在同排,达到目标有两种方案:自身使用第二个操作或是中间的所有数都使用了第二个操作。
• 问题转化为了区间取 max m a x 。直接循环扫,期望得分 60∼80 60 ∼ 80 。
100 100 分算法1:
使用一些方法优化到 O(nlogn) O ( n l o g n ) 就可以获得满分。
这里放一下两种方法的代码:
1.线段树版代码:
#include <cstdio>
#include <utility>
#include <algorithm>
using namespace std;
typedef pair< int,pair<bool,int> > PR;
#define mp make_pair
#define fi first
#define sc second
int i,n,a[2][1000010],ls[1000010],t,pos[2][1000010],c[2][4000010],ans;
PR b[2000010];
inline char gc()
{
static char buf[1000001],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2) ? EOF : *p1++;
}
inline void re(int &x)
{
x=0;
char ch=gc();
while(ch<'0'||ch>'9') ch=gc();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48), ch=gc();
}
void build(int p,int l,int r,int rt)
{
if(l==r)
{
c[p][rt]=a[p][l];
return;
}
int mid=(l+r)>>1;
build(p,l,mid,rt<<1);
build(p,mid+1,r,rt<<1|1);
c[p][rt]=max(c[p][rt<<1],c[p][rt<<1|1]);
}
int getmax(int p,int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R) return c[p][rt];
int now=0,mid=(l+r)>>1;
if(L<=mid) now=getmax(p,l,mid,L,R,rt<<1);
if(R>mid) now=max(now,getmax(p,mid+1,r,L,R,rt<<1|1));
return now;
}
int main()
{
re(n);
for(i=1;i<=n;i++) re(a[0][i]), b[i]=mp(a[0][i],mp(0,i));
for(i=1;i<=n;i++) re(a[1][i]), b[n+i]=mp(a[1][i],mp(1,i));
sort(b+1,b+2*n+1);
for(i=1;i<=2*n;i++)
{
t=(i+1)/2;
if(b[i].sc.fi) a[1][b[i].sc.sc]=t;
else a[0][b[i].sc.sc]=t;
ls[t]=b[i].fi;
}
build(0,1,n,1);
for(i=1;i<=n;i++)
{
t=a[0][i];
if(!pos[0][t]) pos[0][t]=i;
else
if(i-pos[0][t]>1)
ans=max(ans,min(t,getmax(0,1,n,pos[0][t]+1,i-1,1)));
}
build(1,1,n,1);
for(i=1;i<=n;i++)
{
t=a[1][i];
if(!pos[1][t]) pos[1][t]=i;
else
{
if(i-pos[1][t]>1)
ans=max(ans,min(t,getmax(1,1,n,pos[1][t]+1,i-1,1)));
pos[1][t]=-1;
}
}
for(i=1;i<=n;i++)
if(pos[1][i]>0)
ans=max(ans,i);
printf("%d",ls[ans]);
return 0;
}
ST S T 表版:
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <bitset>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long ll ;
#define rep(i, a, b) for (int i = a; i <= b; ++ i)
const int N = 1e6 + 5 ;
using namespace std ;
int n, m, a[2][N], mx[2][N][21], val[N] ;
map <int, int> id ;
struct node {
int a, b ;
} b[N] ;
int GetMx(int k, int l, int r) {
if (k) {
l -= n ;
r -= n ;
}
if (l > r) return 0 ;
int i = floor(log(r - l + 1) / log(2)) ;
return max(mx[k][l][i], mx[k][r - (1 << i) + 1][i]) ;
}
int main() {
scanf("%d", &n) ;
m = 0 ;
int x, Mx = 0 ;
rep(i, 1, n) {
scanf("%d", &x) ;
if (!id[x]) {
id[x] = ++ m ;
val[m] = x ;
x = id[x] ;
a[0][i] = x ;
b[x].a = i ;
} else {
x = id[x] ;
a[0][i] = x ;
b[x].b = i ;
}
}
rep(i, 1, n) {
scanf("%d", &x) ;
if (!id[x]) {
id[x] = ++ m ;
val[m] = x ;
x = id[x] ;
a[1][i] = x ;
b[x].a = n + i ;
} else {
x = id[x] ;
a[1][i] = x ;
b[x].b = n + i ;
}
}
rep(k, 0, 1) rep(i, 1, n) {
mx[k][i][0] = val[a[k][i]] ;
}
rep(k, 0, 1) rep(j, 1, 20) rep(i, 1, (n - (1 << j)) + 1) {
mx[k][i][j] = max(mx[k][i][j - 1], mx[k][i + (1 << (j - 1))][j - 1]) ;
}
int ans = 0 ;
rep(i, 1, m) {
if ((b[i].a - 1) / n == (b[i].b - 1) / n) {
ans = max(ans, min(val[i], GetMx((b[i].a - 1) / n, b[i].a + 1, b[i].b - 1))) ;
} else {
ans = max(ans, val[i]) ;
Mx = max(Mx, val[i]) ;
}
}
printf("%d\n", ans) ;
return 0 ;
}
同样也可以用树状数组,这里不给代码了。
100分算法2:
考虑二分答案。
我们可以把 ≤ ≤ mid m i d 的那些数全部看做消失,从序列当中移除。
O(n) O ( n ) 地 check c h e c k 剩下的序列是否数对相邻。
时间复杂度 O(nlogW)。 O ( n l o g W ) 。
二分答案版:
#include <bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int N = 1000010 ;
int n,ans ;
int a[N],b[N],A[N],B[N] ;
bool check(int x){
int L=0,R=0;
for (int i=1;i<=n;i++) if (a[i]>x) A[++L]=a[i] ;
for (int i=1;i<=n;i++) if (b[i]>x) B[++R]=b[i] ;
if (L==1 || R==1) return false ;
for (int i=1;i<=L;i++) if (A[i-1]!=A[i] && A[i]!=A[i+1]) return false ;
for (int i=1;i<=R;i++) if (B[i-1]!=B[i] && B[i]!=B[i+1]) return false ;
return true ;
}
int main(){
scanf("%d",&n) ;
for (int i=1;i<=n;i++) scanf("%d",&a[i]) ;
for (int i=1;i<=n;i++) scanf("%d",&b[i]) ;
int l=0,r=1e9 ;
while(l<=r){
int mid=(l+r)>>1 ;
if (check(mid)) {
r=mid-1 ;
ans=mid ;
}
else {
l=mid+1 ;
}
}
printf("%d\n",ans) ;
}
B.简单题
得分:70分
预期得分:100分
题目大意
• 确定一个 x x ,最小化
题解
这是个真水题
直接枚举 −3000 − 3000 到 3000 3000 也没关系。
枚举最后的 x x ,容易发现 的取值不会在序列取值之外。
不开 long l o n g long l o n g 会挂掉 30 30 分。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <bitset>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <string>
#include <functional>
#include <climits>
using namespace std ;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
typedef unsigned long long ull ;
typedef long long ll ;
const ll inf = LONG_MAX/2;
inline int read(){
char c ;
int f=1 ;
while((c=getchar())<'0' || c>'9') if (c=='-') f=-1 ;
int res=c-'0' ;
while((c=getchar())>='0' && c<='9') res=res*10+c-'0' ;
return res*f ;
}
const int N = 3010 ;
ll n,Min,Max ;
ll a[N] ;
int main(){
Min=inf,Max=-inf ;
scanf("%lld",&n) ;
for (int i=1;i<=n;i++) {
scanf("%lld",&a[i]) ;
Min=min(a[i],Min) ;
Max=max(a[i],Max) ;
}
ll sum=inf ;
for (ll i=Min;i<=Max;i++){
ll t=0 ;
for (int j=1;j<=n;j++){
t+=(a[j]-i)*(a[j]-i) ;
}
if (t<sum) sum=t ;
}
printf("%lld\n",sum) ;
}
C.串串
得分:20分
预期得分:80~100分
题目大意
对于 s s 的每个前缀,输出它最少能被划分成多少个由恰好 个不同的字符组成的子串。
40 40 分算法:
一看就是 dp d p
• 令 fi f i 表示以 i i 结尾的前缀的答案。枚举,如果 sj+1 s j + 1 ∼ i i 是好的字符串那么就用更新 fi f i 。
• O(n) O ( n ) 级别的 check c h e c k 期望得分 40 40 。
60 60 ~ 100 100 分算法:
• 从后往前枚举 j j ,边枚举边维护,可以做到总复杂度。加点剪枝,期望得分 60 60 ∼ 100 100 。
100分算法:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <bitset>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <string>
#include <functional>
using namespace std ;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
typedef unsigned long long ull ;
typedef long long ll ;
const int N = 200005 ;
const int inf = (1<<29) ;
int n,sum ;
char str[N] ;
int f[N] ;
int cnt[30] ;
int main(){
scanf("%d",&n) ;
scanf("%s",str) ;
str=" "+str ;
int len=strlen(str) ;
for (int i=1;i<len;i++) f[i]=inf ;
for (int i=1;i<len;i++){
sum=0 ;
memset(cnt,0,sizeof(cnt)) ;
for (int j=i;j>0;j++){
if (cnt[str[j]-'a'+1]==0) sum++ ;
cnt[str[j]-'a'+1]++ ;
if (sum==n) f[i]=min(f[i],f[j-1]+1) ;
if (sum>n) break ;
}
if (f[i]==inf) printf("-1\n") ;
else printf("%d\n",f[i]) ;
}
}
48 48 行是一个优化,删去就变成 80 80 分了
D.天天
得分:30分
预期得分:50分
题目大意
• 给出若干个 01 串,找出与所有的串的最近距离最大的 01 串,并输出个数。(这个题意是经过精炼的)
30 30 分算法:
直接暴力枚举所有可能的 01 串,并计算它与每个已有 01 串的距离,期望得分 30 30 。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <bitset>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <string>
#include <functional>
using namespace std ;
const int inf =(1<<29) ;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
typedef unsigned long long ull ;
typedef long long ll ;
inline int read(){
char c ;
int f=1 ;
while((c=getchar())<'0' || c>'9') if (c=='-') f=-1 ;
int res=c-'0' ;
while((c=getchar())>='0' && c<='9') res=res*10+c-'0' ;
return res*f ;
}
const int N = 100010 ;
int n,m,ans,anssum ;
char a[N][20];
int b[N][20] ;
int diff[N] ;
int sum[20][2] ;
int use[20] ;
int judge(){
int Max=0 ;
for (int i=1;i<n;i++){
int t=0;
for (int j=1;j<=m;j++){
if (use[j]==b[i][j]) t++ ;
}
if (t>Max) Max=t;
}
return Max ;
}
void dfs(int k){
if (k==m+1){
int t=judge() ;
if (ans>t) {
ans=t ;
anssum=1;
}
else if (ans==t) anssum++ ;
return ;
}
use[k]=0 ;
dfs(k+1) ;
use[k]=1;
dfs(k+1) ;
}
int main(){
scanf("%d %d",&n,&m) ;
for (int i=1;i<n;i++) scanf("%s",&a[i]) ;
for (int i=1;i<n;i++){
for (int j=0;j<m;j++){
b[i][j+1]=a[i][j]-'0' ;
}
}
ans=inf,anssum=0 ;
memset(use,0,sizeof(use)) ;
dfs(1) ;
cout<<ans<<" "<<anssum<<"\n" ;
}
50 50 分算法:
基于原来 30 30 分的算法。
对于 20 20 % 的特殊性质数据部分,直接输出 1 1 即可。
100 100 分算法:
将题目看成是一张 2m 2 m 个点的图,读入的 n−1 n − 1 个 01 串是起点。
每个点都与与自己只差一位的点有连边,边权为1。
要找 dist d i s t 最大的点,并输出个数。
直接 bfs b f s 即可获得满分。
#include <bits/stdc++.h>
using namespace std ;
const int N = 100000 ;
const int M = 21 ;
queue <int> q ;
int dist[(1<<M)] ;
int n,m,ans,anssum ;
int read(){
int x=0,f=1;char c;
while(c<'0'||c>'9'){if(c=='-') f=-f;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(c^48);c=getchar();}
return x*f;
}
void bfs(){
while (!q.empty()){
int now=q.front();q.pop() ;
if (dist[now]==ans) anssum++ ;
else if (dist[now]>ans){
ans=dist[now];
anssum=1 ;
}
for (int i=0;i<m;i++){
int to=now^(1<<i) ;
if (dist[to]>dist[now]+1){
dist[to]=dist[now]+1 ;
q.push(to) ;
}
}
}
}
int main(){
scanf("%d%d",&n,&m) ;
memset(dist,0x3f,sizeof(dist)) ;
for (int i=1;i<n;i++){
int t=read() ;
q.push(t) ;
dist[t]=0 ;//起点
}
bfs();
printf("%d %d\n",m-ans,anssum) ;
return 0 ;
}
E.摆棋子
得分:30分
预期得分:30+~60分(但我觉得60拿不到,还要加强代码能力)
题目大意
• 在 n∗m n ∗ m 的网格上放棋子,每行每列最多三个。问方案数。
30 30 分算法:
枚举所有可能的棋子方案(一共 2nm 2 n m 种) 然后直接判断。期望得分 30 30 分。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cctype>
#include <cstdlib>
#include <bitset>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
#include <string>
#include <functional>
using namespace std ;
#define mp make_pair
#define pb push_back
#define fi first
#define se second
typedef unsigned long long ull ;
typedef long long ll ;
inline int read(){
char c ;
int f=1 ;
while((c=getchar())<'0' || c>'9') if (c=='-') f=-1 ;
int res=c-'0' ;
while((c=getchar())>='0' && c<='9') res=res*10+c-'0' ;
return res*f ;
}
const int mod = 998244353 ;
int n,m,ans ;
int useh[100],usel[100] ;
void dfs(int x,int y){
if (x==1 && y==m+1){
ans=((ans%mod)+1)%mod ;
return ;
}
if (x<n) dfs(x+1,y) ;
else dfs(1,y+1) ;
if (useh[x]<3 && usel[y]<3){
useh[x]++ ;
usel[y]++ ;
if (x<n) dfs(x+1,y) ;
else dfs(1,y+1) ;
useh[x]-- ;
usel[y]-- ;
}
}
int main(){
scanf("%d %d",&n,&m) ;
ans=0 ;
memset(useh,0,sizeof(useh)) ;
memset(usel,0,sizeof(usel)) ;
dfs(1,1) ;
cout<<ans<<endl ;
}
60
60
分算法:
考虑
min(n,m)≤8
m
i
n
(
n
,
m
)
≤
8
的这些点。
状压每个行是放了 0 个,1 个,2 个还是 3 个。转移就直接枚举当前列的状态是什么就可以了。
O(4∗min(n,m)nm) O ( 4 ∗ m i n ( n , m ) n m )
100
100
分算法:
• 显然我们对具体哪些行是放了 0 个,1 个,2 个还是 3 个并不感兴趣。我们感兴趣的只是这样放的行的个数。
• f[i][a][b][c] f [ i ] [ a ] [ b ] [ c ] 表示现在考虑到了第 i i 列,已经有 行放了 0 0 个, 行放了 1 1 个, 行放了 2 2 个。(那么显然有 行放了3 个)
• 每次转移的时候枚举这一列的几个棋子分别放到了之前有多少个棋子的行里。
• 最好用 dfs d f s 来转移,如果人工手写讨论的话可能会欲仙欲死。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int mod = 998244353 ;
int n,m;
ll f[69][69][69][69];
ll ans=0;
ll C(int x,int y){
if(x==3) return (y*(y-1)*(y-2)/6)%mod;
if(x==2) return (y*(y-1)/2)%mod;
if(x==1) return y%mod;
if(x==0) return 1;
}
int main(){
scanf("%d%d",&n,&m);
memset(f,0,sizeof(f));
f[0][m][0][0]=1;
for(int i=0;i<=n;i++){
for (int j=0;j<=m;j++)
for (int k=0;k<=m-j;k++)
for (int l=0;l<=m-j-k;l++)
for (int jj=0;jj<=3;jj++)
for (int kk=0;kk<=3-jj;kk++)
for (int ll=0;ll<=3-jj-kk;ll++){
f[i+1][j-jj][k-kk+jj][l-ll+kk]+=f[i][j][k][l]*C(jj,j)*C(kk,k)*C(ll,l);
f[i+1][j-jj][k-kk+jj][l-ll+kk]%=mod;
}
if(i==n) ans=(ans+f[n][j][k][l])%mod;
}
ans%=mod;
printf("%lld\n",ans);
return 0;
}
总分 150 150 分。
整体非常不满意。
预期: 360 360 ~ 410 410
下次还要再努力!!!