PKUCPC2021 简略题解
A
题意简述:有一个\(n*m\)的点阵,每两个点之间能连边当且仅当他们距离为\(3\)或\(sqrt 5\),定义一种边的选取方案是合法的要求边集内任意两条边不能共用一个端点。求边集的最大大小并输出一种方案。\(n\times m\leq 2\times 10^5\)
并不会什么很靠谱的解法。
把每条边建出来然后二分图最大独立集即可,注意到边数不会超过\(12nm\),所以直接跑即可。
B
大模拟没什么好写的。
并没有写代码,把同队的人的代码扒来了
#include<cstdio>
#include<algorithm>
using namespace std;
int T,n;
struct zj{
int id,win,lost,same,gd,val,goal;
int xgoal,xval,xgd;
bool operator < (const zj &x)const{
return val>x.val;
}
}k[111];
struct zyy{
int c,d;
}s[111][111];
//if(s[x.id][y.id].c!=s[x.id][y.id].d)return s[x.id][y.id].c>s[x.id][y.id].d;
bool cmp(const zj &x,const zj &y){
//if(x.val!=y.val)return x.val>y.val;
if(x.xval!=y.xval)return x.xval>y.xval;
if(x.xgd!=y.xgd)return x.xgd>y.xgd;
if(x.xgoal!=y.xgoal)return x.xgoal>y.xgoal;
if(x.gd!=y.gd)return x.gd>y.gd;
if(x.goal!=y.goal)return x.goal>y.goal;
return x.id<y.id;
}
int get(){
scanf("%d",&n);
for(int i=1;i<=n;i++)k[i].id=i,k[i].win=k[i].lost=k[i].same=k[i].gd=k[i].val=k[i].goal=k[i].xgoal=k[i].xval=k[i].xgd=0;
for(int i=1;i<=(n-1)*n/2;i++){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
s[a][b]=(zyy){c,d};
s[b][a]=(zyy){d,c};
k[a].goal+=c;
k[b].goal+=d;
if(c!=d){
k[a].win+=(c>d?1:0);
k[a].lost+=(c>d?0:1);
k[a].gd+=c-d;
k[a].val+=(c>d?3:0);
k[b].gd+=d-c;
k[b].win+=(d>c?1:0);
k[b].lost+=(d>c?0:1);
k[b].val+=(d>c?3:0);
}
else{
k[a].same++;k[b].same++;
k[a].val++;k[b].val++;
}
}
sort(k+1,k+1+n);
int l=1,r=1;
while(l<=n){
while(k[l].val==k[r+1].val&&r<n)r++;
for(int p=l;p<r;p++){
for(int i=p;i<=r;i++)k[i].xgoal=k[i].xval=k[i].xgd=0;
for(int i=p;i<=r;i++){
for(int j=i+1;j<=r;j++){
k[i].xgoal+=s[k[i].id][k[j].id].c;
k[j].xgoal+=s[k[i].id][k[j].id].d;
k[i].xgd+=s[k[i].id][k[j].id].c-s[k[i].id][k[j].id].d;
k[j].xgd-=s[k[i].id][k[j].id].c-s[k[i].id][k[j].id].d;
if(s[k[i].id][k[j].id].c!=s[k[i].id][k[j].id].d){
k[i].xval+=(s[k[i].id][k[j].id].c>s[k[i].id][k[j].id].d?3:0);
k[j].xval+=(s[k[i].id][k[j].id].d>s[k[i].id][k[j].id].c?3:0);
}
else{
k[i].xval++;k[j].xval++;
}
}
}
for(int i=p+1;i<=r;i++){
if(cmp(k[i],k[p])){
swap(k[i],k[p]);
}
}
}
l=r=r+1;
}
for(int i=1;i<=n;i++){
printf("%d %d %d %d %d %d %d %d\n",i,k[i].id,k[i].val,k[i].win,k[i].same,k[i].lost,k[i].gd,k[i].goal);
}
return 0;
}
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
scanf("%d",&T);
while(T--)get();
return 0;
}
C
题意简述:每一次操作可以把\(a (b c)\)转化成\((a b) c\)或倒着转换,\(a,b,c\)都可以是变量或者表达式,给定\(s\)和\(t\)两个只含变量和括号的字符串,构造一种方案使得\(s\)变成\(t\),\(s\)中的变量个数不超过\(100\),步数不超过\(200\)
因为步数是变量数的\(2\)倍所以考虑先将\(s\)改成一个字符串,再将这个字符串变成\(t\)。这里钦定这个字符串是\((x1(x2(x3……)))\)
由此我们可以很容易地得到一个构造方案:找到最左边的不符合要求的括号然后移到符合要求的地方即可,每次\(O(n)\)的移动次数。
code:
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define eps (1e-6)
#define N 100
#define ll long long
#define mod1 998244353
#define mod2 1000000007
using namespace std;
int T,n,m,head,a[N+5<<2],b[N+5<<2],c[N+5<<2][N+5<<2],d[N+5<<2][N+5<<2],ch,dh,ans,las,net,now;
char s;
I void input(int *a){
re int i,head=0;for(i=1;i<=m;i++){
s=getchar();while(s!='x'&&s!='('&&s!=')') s=getchar();
if(s=='(') a[i]=101;
else if(s==')') a[i]=102;
else scanf("%d",&a[i]);
}
}
I int geta(int x){
int j;
for(ans=0,j=1;a[j]!=x;j++) ans+=(a[j]==101);return ans;
}
I int getb(int x){
int j;
for(ans=0,j=1;b[j]!=x;j++) ans+=(b[j]==101);return ans;
}
I int swap(int &a,int &b){a^=b^=a^=b;}
I void movea(int x,int y){for(int i=x;i<y;i++) swap(a[i],a[i+1]);}
I void moveb(int x,int y){for(int i=x;i<y;i++) swap(b[i],b[i+1]);}
int main(){
freopen("1.in","r",stdin);
re int i,j,k,h;scanf("%d",&T);
while(T--){
scanf("%d",&n);m=3*n-2;input(a);input(b);ch=dh=1;for(i=1;i<=m;i++)c[1][i]=a[i],d[1][i]=b[i];
for(i=1;i<n;i++){
while(geta(i)!=i){//printf("%d\n",geta(i));
for(j=1;j<=m;j++) if(a[j]==i) {now=j;break;}
for(ans=0,j=1;j<=now;j++) {
ans+=(a[j]==101);
if(ans>i) {las=j;break;}
}
for(ans=0,j=las;j<=m;j++){
ans+=(a[j]==101)-(a[j]==102);
if(!ans) {net=j;break;}
}
for(j=las+1;j<=m;j++){
ans+=(a[j]==101)-(a[j]==102);
if(!ans){k=j;break;}
}
for(j=net+1;j<=m;j++){
ans+=(a[j]==101)-(a[j]==102);
if(!ans) {h=j;break;}
}
movea(las,k);movea(net,h);//printf("%d %d %d %d\n",las,now,net,h);
ch++;for(j=1;j<=m;j++) c[ch][j]=a[j];
}
}
for(i=1;i<n;i++){
while(getb(i)!=i){//printf("%d\n",geta(i));
for(j=1;j<=m;j++) if(b[j]==i) {now=j;break;}
for(ans=0,j=1;j<=now;j++) {
ans+=(b[j]==101);
if(ans>i) {las=j;break;}
}
for(ans=0,j=las;j<=m;j++){
ans+=(b[j]==101)-(b[j]==102);
if(!ans) {net=j;break;}
}
for(j=las+1;j<=m;j++){
ans+=(b[j]==101)-(b[j]==102);
if(!ans){k=j;break;}
}
for(j=net+1;j<=m;j++){
ans+=(b[j]==101)-(b[j]==102);
if(!ans) {h=j;break;}
}
moveb(las,k);moveb(net,h);//printf("%d %d %d %d\n",las,now,net,h);
dh++;for(j=1;j<=m;j++) d[dh][j]=b[j];
}
}
printf("%d\n",ch+dh-1);
for(i=1;i<=ch;i++){
for(j=1;j<=m;j++){
if(c[i][j]==101) printf("(");
else if(c[i][j]==102)printf(")"),(c[i][j+1]!=102)&&(printf(" "));
else printf("x%d",c[i][j]),(c[i][j+1]!=102)&&(printf(" "));
}
printf("\n");
}
for(i=dh-1;i>=1;i--){
for(j=1;j<=m;j++){
if(d[i][j]==101) printf("(");
else if(d[i][j]==102)printf(")"),(d[i][j+1]!=102)&&(printf(" "));
else printf("x%d",d[i][j]),(d[i][j+1]!=102)&&(printf(" "));
}
printf("\n");
}
printf("\n");
}
}
E
题意简述:询问区间和和区间最大值并进行一些加密操作
没什么好说的,二维前缀和和二维st表即可。
code:
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define eps (1e-6)
#define N 1000
#define ll long long
#define mod1 998244353
#define mod2 1000000007
using namespace std;
int n,m,len,lg[N+5],a[N+5][N+5],st[N+5][N+5][11];ll b[N+5][N+5],c[N+5][N+5],q[N+5][N+5],po1[N+5],po2[N+5],ans;
I ll find(int x,int y,int a,int b){return q[a][b]-q[a][y-1]-q[x-1][b]+q[x-1][y-1];}
I ll check(int x,int y,int a,int b){
int now=lg[a-x+1];return max(max(st[x][y][now],st[a-(1<<now)+1][y][now]),max(st[x][b-(1<<now)+1][now],st[a-(1<<now)+1][b-(1<<now)+1][now]));
}
int main(){
freopen("1.in","r",stdin);
re int i,j,k;scanf("%d%d%d",&n,&m,&len);
for(i=2;i<=n;i++) lg[i]=lg[i/2]+1;
for(i=1;i<=n;i++){
for(j=1;j<=n;j++) scanf("%d",&a[i][j]),q[i][j]=q[i-1][j]+q[i][j-1]-q[i-1][j-1]+a[i][j];
}
for(i=n;i;i--){
for(j=n;j;j--){
for(st[i][j][0]=a[i][j],k=1;i+(1<<k)-1<=n&&j+(1<<k)-1<=n;k++) st[i][j][k]=max(max(st[i][j][k-1],st[i+(1<<k-1)][j][k-1]),max(st[i+(1<<k-1)][j+(1<<k-1)][k-1],st[i][j+(1<<k-1)][k-1]));
}
}
for(i=1;i<=(n-m)/len+1;i++){
for(j=1;j<=(n-m)/len+1;j++) c[i][j]=find((i-1)*len+1,(j-1)*len+1,(i-1)*len+m,(j-1)*len+m)%mod2;
}
for(i=1;i<=(n-m)/len+1;i++){
for(j=1;j<=(n-m)/len+1;j++) b[i][j]=check((i-1)*len+1,(j-1)*len+1,(i-1)*len+m,(j-1)*len+m);
}
po1[0]=po2[0]=1;
for(i=1;i<=n;i++)po1[i]=po1[i-1]*39%mod1,po2[i]=po2[i-1]*37%mod2;
for(i=1;i<=(n-m)/len+1;i++){
for(ans=0,j=1;j<=(n-m)/len+1;j++) ans+=b[i][j]*po1[j-1]%mod1;printf("%lld\n",ans%mod1);
}
for(i=1;i<=(n-m)/len+1;i++){
for(ans=0,j=1;j<=(n-m)/len+1;j++) ans+=c[i][j]*po2[j-1]%mod2;printf("%lld\n",ans%mod2);
}
}
G
题意简述:有一个长方形盒子,内有一个点樱桃,要放一个圆形且不能越出边界和碰到樱桃。求最大半径长度
二分半径,然后把盒子缩小,判断樱桃和四个点距离和半径距离即可。
code:
#include<cstdio>
#include<cmath>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define eps (1e-6)
using namespace std;
int n,m,k,x,y,z,a,b;db l,r,mid;
I db dis(db x,db y,db a,db b){return sqrt((x-a)*(x-a)+(y-b)*(y-b));}
I int check(db mid){
if(2*mid<n&&2*mid<m&&(dis(a,b,n-mid,m-mid)>mid||dis(a,b,mid,mid)>mid||dis(a,b,mid,m-mid)>mid||dis(a,b,n-mid,mid)>mid)) return 1;return 0;
}
int main(){
freopen("1.in","r",stdin);
re int i;scanf("%d%d%d%d",&n,&m,&a,&b);l=0;r=max(n,m);
while(l+eps<r) mid=(l+r)/2,(check(mid)?l:r)=mid;printf("%.9Lf\n",l);
}
I
题意简述:有\(n\)个人,编号\(1\)到\(n\),放在一个圆桌上,每个人的权值为向两边走第一个编号大于它的人和他的距离,求一种排列使得所有人的权值和最大。
\(n\)在int范围内。
首先人类智慧一下就可以得到一定是先放在中点,然后放在分成的两个半圆的中点,重复。
设\(f(n)\)为有\(n\)个点的权值。
那么可以得到递推式\(f(n)=\frac{n-1}{2}+1+f(\frac{n-1}{2})+f(\frac{n}{2})\)
这个东西直接递推是\(O(n)\)的。
然而这个东西其实和\(ZJOI2012\)数列很想,状态数不会超过\(logn\)个所以开个map即可。
code:
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#include<map>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define eps (1e-6)
#define N 100000
#define ll long long
using namespace std;
ll n,ans;map<ll,ll> f;
I ll dfs(ll x){
if(x==1) return 1;if(!x) return 0;
if(f[x]) return f[x];return f[x]=dfs((x-1)/2)+dfs(x-1-(x-1)/2)+(x-1)/2+1;
}
int main(){
freopen("1.in","r",stdin);
register int i;scanf("%lld",&n);ans=n/2;
printf("%lld\n",dfs(n-1)+n/2);
}
J
有\(n\)棵树,每棵树的影长为\(h_i\),要求每棵树之间距离大于等于\(A\),求影子覆盖的最小范围。\(n\leq 2\times 10^5\)
人类智慧一下就可以知道就是直接排序即可。
code:
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define eps (1e-6)
#define N 100000
#define ll long long
using namespace std;
int n,m,T;ll ans,pl[N+5],a[N+5],A,l[N+5],maxn=1e18;
int main(){
freopen("1.in","r",stdin);
re int i;scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&A);for(i=1;i<=n;i++)scanf("%lld",&a[i]);sort(a+1,a+n+1);ans=0;maxn=1e18;
for(i=1;i<=n;i++) pl[i]=A*i,l[i]=pl[i]-a[i]+1;
for(i=n;i;i--){
maxn=min(maxn,l[i]);if(i!=1&&maxn>pl[i-1]) ans+=maxn-pl[i-1]-1;
}
printf("%lld\n",A*n-maxn+1-ans);
}
}
K
题意简述:一个棋盘,一个骑士跳日字格,从起点到终点求最小步数,且输出方案。
直接bfs即可。
code:
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#define re register
#define db long double
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define eps (1e-6)
#define N 100
using namespace std;
int n,m,stx,sty,enx,eny,a[N+5][N+5],nowx,nowy,head,f[N+5][N+5];
int xp[8]={1,1,-1,-1,2,2,-2,-2};
int yp[8]={2,-2,2,-2,-1,1,-1,1};
struct yyy{int x,y;}now,from[N+5][N+5],b[N*N+5];
queue<yyy> q;
int main(){
freopen("1.in","r",stdin);
re int i,j,k;
while(1){
scanf("%d",&n);if(n==-1) break;scanf("%d",&m);memset(f,0,sizeof(f));
for(i=1;i<=n;i++){
for(j=1;j<=m;j++) scanf("%d",&a[i][j]);
}
scanf("%d%d%d%d",&stx,&sty,&enx,&eny);stx++;sty++;enx++;eny++;f[stx][sty]=1;
while(!q.empty()) q.pop();q.push((yyy){stx,sty});
while(!q.empty()){
now=q.front();q.pop();
for(k=0;k<=7;k++){
nowx=now.x+xp[k];nowy=now.y+yp[k];if(now.x<1||now.y<1||now.x>n||now.y>m||a[nowx][nowy])continue;
if(!f[nowx][nowy]) q.push((yyy){nowx,nowy}),f[nowx][nowy]=f[now.x][now.y]+1,from[nowx][nowy]=(yyy){now.x,now.y};
}
}
if(!f[enx][eny]) {printf("0\n");continue;}
printf("%d\n",f[enx][eny]-1);now=(yyy){enx,eny};
while(now.x!=stx||now.y!=sty) b[++head]=now,now=from[now.x][now.y];
printf("(%d,%d)",stx-1,sty-1);for(i=head;i;i--) printf(",(%d,%d)",b[i].x-1,b[i].y-1);printf("\n");head=0;
}
}