9月30日考试 题解(贪心+搜索+同余)
100+65+21=186.同余没想出来太可惜了,本来能上200的QAQ
--------------------
T1 指引
题目大意:给定$n$个人的坐标和$n$个门的坐标,每个人只能增大自己的横纵坐标,每个门只能让一个人进。问最多能有几个人走进门。$n\leq 1e5$
贪心。显然如果一个人$(x_1,y_1)$能走到门$(x_2,y_2)$,那么肯定有$x_1 \leq x_2 ,y_1 \leq y_2$。先按照$x$排序,然后考虑每个门所作出的贡献:把横坐标合法的人的纵坐标用数据结构维护一下,看有没有人纵坐标也合法。我用的STL::set。其他数据结构诸如线段树或树状数组也可以。
代码:
#include<cstdio> #include<iostream> #include<set> #include<algorithm> using namespace std; const int N=100005; int n,num,l=1,cnt,now=1,ans; struct node { int x,y,id; }a[N],b[N],c[N],d[N],ex[N]; set<int> s; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } bool cmp(node x,node y) { if (x.x==y.x) return x.y<y.y; return x.x<y.x; } int main() { num=read();n=read(); for (int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].id=i; for (int i=1;i<=n;i++) b[i].x=read(),b[i].y=read(); for (int i=1;i<=n;i++) c[i]=a[i],d[i]=b[i]; sort(c+1,c+n+1,cmp); sort(d+1,d+n+1,cmp); while(d[l].x<c[1].x) l++; for (int i=l;i<=n;i++) ex[++cnt]=d[i]; sort(ex+1,ex+cnt+1,cmp); sort(a+1,a+n+1,cmp); for (int i=1;i<=cnt;i++) { while(now<=n&&ex[i].x>=a[now].x) s.insert(a[now].y),now++; set<int>::iterator tmp=s.lower_bound(ex[i].y); if (tmp!=s.begin()) tmp--,ans++,s.erase(tmp); } printf("%d",ans); return 0; }
T2 碎片
题目大意:给定一个$n*m$的字符图案,多组数据,问是否能通过行和列的变换使得图案变成一个中心对称图形。$n,m\leq 12$。
爆搜+特判理论上能有70pts.正解实质上就是优化的爆搜。
首先有如下观察:
1.每一行/列的可重集合不变。
2.题目要求使得我们可以任意交换行/列的顺序。
3.对于能够中心对称的两个行/列A和B,它们的集合相同。
4.如果存在中心对称图形,那么任意交换两个对称的行/列,图形仍然是中心对称图形。
有如上性质,我们可以考虑搜索。先枚举行/列,若行/列为奇数,那么先固定中间的一行/列,然后每次枚举一对对称的行/列。单次枚举的复杂度M上限11*9*7*5*3*1=10395。时间复杂度为$O(nm10395^2)$。
但实际上我们可以预处理出每两行/两列的集合是否相同,然后利用观察3进行剪枝。实际上的复杂度远远达不到上限,甚至跑的飞起(最慢也只用了2ms)。好多人各种玄学做法也取得了不错的分数:随机化、同构、爆搜等等。怕不是出题人用脚造数据……
代码:
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N=15; char mp[N][N]; int num,T,n,m,r[N],l[N],flag; bool row[N][N],line[N][N],visr[N],visl[N]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void check() { for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (mp[r[i]][l[j]]!=mp[r[n-i+1]][l[m-j+1]]) return; printf("YES\n"); flag=1; } inline void solvel(int pos,int type) { if (flag) return; if (pos==0) { check(); return; } if (type) { for (int i=1;i<=m;i++) { visl[i]=1; l[pos]=i; solvel(pos-1,0); visl[i]=0; } } else { for (int i=1;i<=m;i++) { if (visl[i]) continue; for (int j=i+1;j<=m;j++) if (!visl[j]&&line[i][j]) { visl[i]=1;visl[j]=1; l[pos]=i;l[m-pos+1]=j; solvel(pos-1,0); visl[i]=0;visl[j]=0; } return; } } } inline void solver(int pos,int type) { if (flag) return; if (pos==0) { solvel((m+1)/2,m&1); return; } if (type) { for (int i=1;i<=n;i++) { visr[i]=1; r[pos]=i; solver(pos-1,0); visr[i]=0; } } else { for (int i=1;i<=n;i++) { if (visr[i]) continue; for (int j=i+1;j<=n;j++) if (!visr[j]&&row[i][j]) { visr[i]=1;visr[j]=1; r[pos]=i;r[n-pos+1]=j; solver(pos-1,0); visr[i]=0;visr[j]=0; } return; } } } int main() { num=read();T=read(); while(T--) { n=read(),m=read(); for (int i=1;i<=n;i++) scanf("%s",mp[i]+1); for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { static char x[N],y[N]; for (int k=1;k<=m;k++) x[k]=mp[i][k], y[k]=mp[j][k]; sort(x+1,x+m+1); sort(y+1,y+m+1); row[i][j]=1; for (int k=1;k<=m;k++) if(x[k]!=y[k]) row[i][j]=0; } for (int i=1;i<=m;i++) for (int j=1;j<=m;j++) { static char x[N],y[N]; for (int k=1;k<=n;k++) x[k]=mp[k][i], y[k]=mp[k][j]; sort(x+1,x+n+1); sort(y+1,y+n+1); line[i][j]=1; for (int k=1;k<=n;k++) if (x[k]!=y[k]) line[i][j]=0; } flag=0; solver((n+1)/2,n&1); if (!flag) printf("NO\n"); } return 0; }
T3 寻梦
题目大意:给定$n$和$k$。求出一组解使得$\forall x \in [1,n]$,有$x_i | k$且$\sum x_i=n$。$n\leq 10^{18},k\leq 10^{15}$。不同的$k$的个数不超过50。多组数据。
易得知正整数都可以用质数表示出来,所以我们只需考虑$k$的质因数。所以问题变成了求解形如$\sum a_ix_i=n$的式子。对于不同的$k$,我们分情况讨论:
1.$k$只有1个质因数。我们直接判断$n$能否整除于它即可。
2.$k$有两个质因数。这时问题转化为求解$ax+by=n$。扩展欧几里得处理。
3.$k$有多个质因数。同余最短路处理。选取一个最小的质因数$p$作为$base$,设$f[i]$表示用其他质因数表示的模$p$为$i$的最小的数。有转移形如:$f[i+x]=f[i]+x$。考虑到与最短路中的松弛操作相似,于是可以利用最短路求解。判断是否有解则可以看$f[n \mod \ p]$是否小于等于$n$。考虑到若有解,则$f[n \mod \ p]$与$n$在模$p$意义下同余,若$f[n \mod \ p] \leq n$,那么$f[n \mod \ p]$可以通过加若干次$p$使得其等于$n$。
时间复杂度$O(50\sqrt k + Tp\log k)$。
代码:
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<queue> #define int long long using namespace std; const int N=1000005; const int V=3.2e7+5; const int Q=55; const int inf=1e18; int memk[Q],prime[N*2],vis[V],dis[Q][N],cnt[Q],p[Q][N]; int num,T,n,k,tot,q; struct node { int dis,pos; }; bool operator < (node a,node b) { return a.dis>b.dis; } inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void exgcd(int a,int b,int &x,int &y) { if (!b) { x=1;y=0; } else { exgcd(b,a%b,x,y); int t=x;x=y;y=t-a/b*y; } } inline void solve() { for (int i=2;i<V;i++) { if (!vis[i]) prime[++tot]=vis[i]=i; for (int j=1;j<=tot&&prime[j]<=vis[i];j++) { int tmp=prime[j]*i; if (tmp>=V) break; vis[tmp]=prime[j]; } } } inline void spfa(int pos) { priority_queue<node> q; for (int i=0;i<p[pos][1];i++) dis[pos][i]=inf; dis[pos][0]=0;q.push((node){0,0}); while(!q.empty()) { node now=q.top();q.pop(); for (int i=2;i<=cnt[pos];i++) { int dist=(now.pos+p[pos][i])%p[pos][1]; if (dis[pos][dist]>now.dis+p[pos][i]) { dis[pos][dist]=now.dis+p[pos][i]; q.push((node){dis[pos][dist],dist}); } } } } signed main() { solve(); num=read(),T=read(); while(T--) { n=read();k=read(); int pos=0; for (int i=1;i<=q;i++) if (memk[i]==k) pos=i; if (pos==0) { pos=++q;memk[q]=k; int tmp=k; for (int i=1;prime[i]*prime[i]<=tmp;i++) { if (tmp%prime[i]==0) { p[pos][++cnt[pos]]=prime[i]; while(tmp%prime[i]==0) tmp/=prime[i]; } } if (tmp!=1) p[pos][++cnt[pos]]=tmp; if (cnt[pos]>=3) spfa(pos); } bool flag=0; for (int i=1;i<=cnt[pos];i++) if (n%p[pos][i]==0) { printf("YES\n"); flag=1; break; } if (flag) continue; if (cnt[pos]<=1){ printf("NO\n"); continue; } if (cnt[pos]<=2) { int x=0,y=0; exgcd(p[pos][1],p[pos][2],x,y); y=(y%p[pos][1]+p[pos][1])%p[pos][1]; int tmp=y*(n%p[pos][1])%p[pos][1]*p[pos][2]; if (tmp<=n) printf("YES\n"); else printf("NO\n"); continue; } int tmp=n%p[pos][1]; if (dis[pos][tmp]<=n) printf("YES\n"); else printf("NO\n"); } return 0; }