T1:涂色游戏
直接开二维数组记录是不可行的,由于每一次操作会覆盖整行整列,所以开两个数组,分别存储行、列的颜色。
由于颜色会覆盖,所以额外记一个时间戳,比较大小输出即可。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=1e5+10;
int n,m,T,q;
struct node{int col,ord;}a[N],b[N];
int main(){
T=rd;
while(T--){
memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
n=rd,m=rd,q=rd;
FOR(i,1,q){
int op=rd,x=rd,c=rd;
if(op==0) a[x].col=c,a[x].ord=i;
else b[x].col=c,b[x].ord=i;
}
FOR(i,1,n){
FOR(j,1,m){
if(a[i].ord>b[j].ord) printf("%d ",a[i].col);
else printf("%d ",b[j].col);
}
cout<<endl;
}
}
return 0;
}
T2:幂次
容易发现,当 k ≥ 3 k\ge 3 k≥3 时,答案会非常小。
观察样例 5 5 5 也可知,当 n = 1 0 18 , k = 3 n=10^{18},k=3 n=1018,k=3 时,答案仅为 1036002 1036002 1036002,约为 n 3 \sqrt[3] n 3n 级别。
由此我们可以暴力枚举底数,然后用 map \text{map} map 维护某个数是否计算过,可以拿到 45 pts \text{45 pts} 45 pts。
然后考虑如何计算 k = 2 k=2 k=2 时的情况,我们已经可以计算 k ≥ 3 k\ge 3 k≥3 的所有数了,所以只需关心 n n n 以内有多少完全平方数,答案是 ⌊ n ⌋ \left \lfloor \sqrt n \right \rfloor ⌊n⌋。但完全平方数也可能是某个形如 a b , b ≥ k a^b,b\ge k ab,b≥k 的数,这个可以在求 k ≥ 3 k\ge 3 k≥3 的情况时顺便判断即可,最后答案减去这些数。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define int long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define debug cout<<"FUCKCCF!"<<endl;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
int n,t,cnt1,cnt2;map<int,bool> mp;
signed main(){
n=rd,t=rd;
if(t==1) printf("%lld\n",n);
else{
for(int i=2;i*i*i<=n;i++){
int k=i*i,power=2;
while(k<=n/i){
k*=i,power++;
if(power<t||mp[k]) continue;
mp[k]=1,cnt1++;
if((int)sqrtl(k)*(int)sqrtl(k)==k) cnt2++;
}
}
if(t==2) printf("%lld\n",(int)sqrtl(n)+cnt1-cnt2);
else printf("%lld\n",cnt1+1);
}
return 0;
}
T3:圣诞树
30 pts \text{30 pts} 30 pts :指数级暴力
n ≤ 18 n\le 18 n≤18,还不知道,是给暴搜加剪枝的分??
看特殊性质 B \text{B} B,发现会得到这样的多边形:
肯定是从
1
1
1 顺着从到
n
n
n,因为角都是钝角,
1
→
3
+
3
→
2
>
1
→
2
+
2
→
3
1\to 3+3\to 2>1\to 2+2\to 3
1→3+3→2>1→2+2→3。
再思考正解,我们走的路径是不是也有某些特殊性质,来简化运算?再画一个凸多边形:
假如说我们要走完这四个点,从
A
\text{A}
A 出发,会发现一定不能出现
AD+BC
\text{AD+BC}
AD+BC,因为三角形两边之和大于第三边,
AD+BC=AE+CE+DE+BE>AC+BD
\text{AD+BC=AE+CE+DE+BE>AC+BD}
AD+BC=AE+CE+DE+BE>AC+BD,也
>AB+CD
\text{>AB+CD}
>AB+CD,所以一定不优。
所以最后一定是从 k k k 开始,在它左边走一段后再在右边走一段,总之路径不会交叉。
发现这个关键性质,就可以考虑区间 dp \text{dp} dp。设 f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1 表示走完 [ i , j ] [i,j] [i,j],当前在 i / j i/j i/j 时的最小路程,输出方案,记录从哪个状态转移过来即可。
不用枚举区间断点,时间复杂度 O ( n 2 ) O(n^2) O(n2)。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define debug cout<<"I LOVE CCF!"<<endl;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=1010;
const double INF=1e18;
int n;double f[N][N][2],pre[N][N][2];
struct node{double x,y;int id;}a[N],tmp[N];
double dis(node a,node b){
double x1=a.x,y1=a.y,x2=b.x,y2=b.y;
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
void print(int i,int j,int op){
if(i==j){printf("%d ",a[i].id);return;}
if(op==0) printf("%d ",a[i].id),print(i+1,j,pre[i][j][op]);
else printf("%d ",a[j].id),print(i,j-1,pre[i][j][op]);
}
int main(){
n=rd;int k=1;
FOR(i,1,n) scanf("%lf%lf",&a[i].x,&a[i].y),a[i].id=i,tmp[i]=a[i];
FOR(i,2,n) if(a[i].y>a[k].y) k=i;
FOR(i,1,k) a[n+i-k]=tmp[i];
FOR(i,k+1,n) a[i-k]=tmp[i];
FOR(len,2,n-1){
FOR(i,1,n-len){
int j=i+len-1;
f[i][j][0]=f[i][j][1]=INF;
if(f[i+1][j][0]+dis(a[i],a[i+1])<f[i+1][j][1]+dis(a[i],a[j]))
f[i][j][0]=f[i+1][j][0]+dis(a[i],a[i+1]),pre[i][j][0]=0;
else f[i][j][0]=f[i+1][j][1]+dis(a[i],a[j]),pre[i][j][0]=1;
if(f[i][j-1][0]+dis(a[i],a[j])<f[i][j-1][1]+dis(a[j],a[j-1]))
f[i][j][1]=f[i][j-1][0]+dis(a[i],a[j]),pre[i][j][1]=0;
else f[i][j][1]=f[i][j-1][1]+dis(a[j],a[j-1]),pre[i][j][1]=1;
}
}
printf("%d ",a[n].id);
if(f[1][n-1][0]+dis(a[1],a[n])>f[1][n-1][1]+dis(a[n-1],a[n])) print(1,n-1,1);
else print(1,n-1,0);
return 0;
}
T4:密码锁
k = 1 k=1 k=1,最大值减最小值。
k = 2 k=2 k=2,考虑一个简单的贪心,把全局最大值和全局最小值分别放在不同行,然后每一行的贡献就可以直接算了。
void solve2(){
while(T--){
n=rd;int mx=0,mn=N,mxl=0,mnl=0,mxa=0,mna=0,ans=0;
FOR(i,0,k-1) FOR(j,1,n){
a[i][j]=rd;
if(a[i][j]>mx) mx=a[i][j],mxl=j,mxa=i;
if(a[i][j]<mn) mn=a[i][j],mnl=j,mna=i;
}
ans=max(mx-a[mna^1][mnl],a[mxa^1][mxl]-mn);
FOR(j,1,n){
if(j==mxl||j==mnl) continue;
int tmp1=max(mx-a[0][j],a[1][j]-mn);
int tmp2=max(mx-a[1][j],a[0][j]-mn);
ans=max(ans,min(tmp1,tmp2));
}
printf("%d\n",ans);
}
}
然后,然后不会了。
还有一个贪心就是考虑每一列的一次转圈对答案的贡献,让总影响最小。但是这是错的,不过只要随机若干次,一定能得到正确的操作序列。因为答案也是对原来的密码锁进行若干次操作,所以随机化是对的。
用这个种子随机化跑了次优解。
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define debug cout<<"????CCF"<<endl;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
const int N=1e5+10,INF=2e9;
int T,n,k,a[N][5],b[5][N],mx[5],mn[5];
void solve1(){
while(T--){
n=rd;int mxx=0,mnn=N;
FOR(i,0,k-1) FOR(j,1,n) b[i][j]=rd,mxx=max(mxx,b[i][j]),mnn=min(mnn,b[i][j]);
printf("%d\n",mxx-mnn);
}
}
void solve2(){
while(T--){
n=rd;int mxx=0,mnn=N,mxl=0,mnl=0,mxa=0,mna=0,ans=0;
FOR(i,0,k-1) FOR(j,1,n){
b[i][j]=rd;
if(b[i][j]>mxx) mxx=b[i][j],mxl=j,mxa=i;
if(b[i][j]<mnn) mnn=b[i][j],mnl=j,mna=i;
}
ans=max(mxx-b[mna^1][mnl],b[mxa^1][mxl]-mnn);
FOR(j,1,n){
if(j==mxl||j==mnl) continue;
int tmp1=max(mxx-b[0][j],b[1][j]-mnn);
int tmp2=max(mxx-b[1][j],b[0][j]-mnn);
ans=max(ans,min(tmp1,tmp2));
}
printf("%d\n",ans);
}
}
int main(){
T=rd,k=rd;
if(k==1){solve1();return 0;}
else if(k==2){solve2();return 0;}
srand(114514);
while(T--){
n=rd;
FOR(i,1,k) FOR(j,1,n) a[j][i]=rd;
int cnt=90,ans=INF;
FOR(i,1,cnt){
random_shuffle(a+1,a+1+n);
memset(mx,-0x3f,sizeof(mx)),memset(mn,0x3f,sizeof(mn));
FOR(j,1,n){
int v=INF,p;
FOR(x,0,k-1){
int t=0;
FOR(y,1,k){
int l=(y+x-1)%k+1;
t=max(t,max(mx[y],a[j][l])-min(mn[y],a[j][l]));
}
if(t<v) v=t,p=x;
}
FOR(y,1,k){
int t=(y+p-1)%k+1;
mx[y]=max(mx[y],a[j][t]),mn[y]=min(mn[y],a[j][t]);
}
if(ans<=v) break;
}
int res=0;
FOR(i,1,k) res=max(res,mx[i]-mn[i]);
ans=min(ans,res);
}
printf("%d\n",ans);
}
return 0;
}