状况
David不在,我跟Fashion两个人打,只A了两题(。)
J. How Much Memory Your Code Is Using?(签到)
solver: Artist
简单模拟,注意getline前有数字,要用getchar冲掉。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int check(string str,int len){
int unit;
while(1){
if(str[0]=='b') {unit=1;break;}
if(str[0]=='c') {unit=1;break;}
if(str[0]=='i') {unit=4;break;}
if(str[0]=='l'&&str[5]=='l') {unit=8;break;}
if(str[0]=='_') {unit=16;break;}
if(str[0]=='f') {unit=4;break;}
if(str[0]=='d') {unit=8;break;}
if(str[0]=='l') {unit=16;break;}
break;
}
int st=0,ed=0;
for(int i=0;i<len;++i){
if(str[i]=='[') st=i+1;
if(str[i]==']') ed=i-1;
}
if(st==0&&ed==0) return unit;
int num=0;
for(int i=st;i<=ed;++i){
num*=10;num+=str[i]-'0';
}
return num*unit;
}
int main() {
int t;scanf("%d",&t);
int ca=0;
while(t--){
int n;scanf("%d",&n);
ll ans = 0;
string str;getchar();
for(int c=1;c<=n;++c){
getline(cin,str);
int len = str.length();
ans+=check(str,len);
}
printf("Case #%d: %lld\n",++ca,(ans+1024-1)/1024);
}
}
C. Insertion Sort(组合数学)
solver: FashionSE
- 题意:permutation(n),前k个数被排序成新数列,这个新数列的最长上升子序列长度为至少n-1.求一开始的permutation有多少种。
- 思路:
- (1)最长上升子序列长度为n: A(k,k)
- (2)最长上升子序列长度为n-1:
- (2.1)前k个中的最大数字为k:前面A(k,k),那么后面n-k个数,为[n-k,n]且满足最长上升子序列长度为n-1。找规律得种类
- 数目=(n-k-1)^2,再乘上前k个的所有排序A(k,k)
- (2.2)前k个中的最大数字为k+1:那么前k个任意一个可以与k+1交换,而这任意一个可以放在n-k所有位置。即k*(n-k)
- (2.3)前k个中的最大数字大于k+1:前k个一定是[1,k-1]和这个数。k可以放在除了第k+1之外(与2.2重复)任意一个位置。(n-k-1)
- 因此答案为ans = A ( k , k ) × ( 1 + ( n − k − 1 ) 2 + k × ( n − k ) + n − k − 1 ) A(k,k)\times(1+(n-k-1)^2+k\times(n-k)+n-k-1) A(k,k)×(1+(n−k−1)2+k×(n−k)+n−k−1)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int t;scanf("%d",&t);
for(int cas = 1;cas<=t;++cas){
ll n,k,q;
ll ans = 1;
scanf("%lld%lld%lld",&n,&k,&q);
if(k>n-1) k = n-1;
for(int i=1;i<=k;++i) ans=ans*i%q;
ans=ans*((n-k-1)*(n-k-1)+(k+1)*(n-k))%q;
printf("Case #%d: %lld\n",cas,ans);
}
}
G. Best ACMer Solves the Hardest Problem(暴力)
- 题意:四个操作,二维平面,有点权,加点、删点,查找某点周围距离\sqrt{k}的点权求和输出、查找并修改某点周围距离
- k \sqrt{k} k的点点权加w。case1e3,初始点数n1e5,操作数m1e5,要求在线,k1e7,xyw6e3。
- 思路:(暴力)观察到,k1e7非常小(作为距离的平方)。因此,暴力出1~1e7每个数为结果的可能的平方组合,存在一个vector之中。
- 然后,对于给定点,和给定距离,遍历该距离的可能组合,对每个组合,都在四个方向上进行试探。
#include <bits/stdc++.h>
using namespace std;
int t,cnt;
const int maxk = 1e7+6;
const int maxn = 1e5+5;
vector<pair<int,int>> G[maxk];
int vis1[10004];
int vis[6004][6004];
int val[6004][6004];
int px[maxn<<1],py[maxn<<1];
typedef long long ll;
ll ans,lastans;
int main(){
scanf("%d",&t);
for(int i=0;i<=1e4;++i){
for(int j=0;j<=1e4;++j){
if(i*i+j*j>1e7) break;
if(i==j){
if(vis1[i]) continue;
else vis1[i]=1;
}
G[i*i+j*j].push_back(make_pair(i,j));
}
}
for(int ca=1;ca<=t;++ca){
printf("Case #%d:\n",ca);
int n,m,opt,x,y;ll w,k;scanf("%d%d",&n,&m);
lastans = cnt = 0;
for(int i=1;i<=n;++i){
scanf("%d%d%lld",&x,&y,&w);
px[++cnt] = x,py[cnt] = y;
vis[x][y] = 1,val[x][y] = w;
}
for(int c=1;c<=m;++c){
scanf("%d%d%d",&opt,&x,&y);
x = (x+lastans)%6000+1,y = (y+lastans)%6000+1;
ans = 0;
if(opt==1){
scanf("%d",&w);
px[++cnt] = x,py[cnt] = y;
vis[x][y] = 1,val[x][y] = w;
}
if(opt==2){
vis[x][y] = val[x][y] = 0;
}
if(opt==3){
scanf("%lld%lld",&k,&w);
for(auto d:G[k]){
int nx[3],ny[3];
nx[1] = x+d.first,nx[2] = x-d.first;
ny[1] = y+d.second,ny[2] = y-d.second;
for(int i=1;i<=2;++i){
if(nx[i]<1||nx[i]>6000) continue; // 注意细节
if(i==2&&nx[i]==nx[i-1]) continue;
for(int j=1;j<=2;++j){
if(ny[j]<1||ny[j]>6000) continue;
if(j==2&&ny[j]==ny[j-1]) continue;
if(vis[nx[i]][ny[j]]) val[nx[i]][ny[j]]+=w;
}
}
}
}
if(opt==4){
scanf("%lld",&k);
for(auto d:G[k]){
int nx[3],ny[3];
nx[1] = x+d.first,nx[2] = x-d.first;
ny[1] = y+d.second,ny[2] = y-d.second;
for(int i=1;i<=2;++i){
if(nx[i]<1||nx[i]>6000) continue;
if(i==2&&nx[i]==nx[i-1]) continue;
for(int j=1;j<=2;++j){
if(ny[j]<1||ny[j]>6000) continue;
if(j==2&&ny[j]==ny[j-1]) continue;
if(vis[nx[i]][ny[j]]) ans+=val[nx[i]][ny[j]];
}
}
}
printf("%lld\n",ans);
lastans = ans;
}
}
for(int i=1;i<=cnt;++i) vis[px[i]][py[i]] = val[px[i]][py[i]] = 0;
}
}
K. Let the Flames Begin(约瑟夫环)
- 题意:1~n的约瑟夫环,求第m个处死者的原序号,步长为k。k,n,m<=1e18,min(n,k)<=2e6
- 思路:首先我们要知道约瑟夫环的递推公式:(约瑟夫环问题:处死第m个,求最后存活者的初始编号) 证明
- 从0开始编号:f(n,m) = (f(n-1,m)+m)%n, f(1,m) = 0
- 从1开始编号:f(n,m) = (f(n-1,m)+m)%n. f(1,m) = 0,最后答案 = f(n,m) + 1.
然后,考虑第m个处死者的原序号如何求得:
当进行到第m轮时,要处死第m个人,此时剩下(n-m+1)个人。
因此,f(n-m+1,m) = (k-1)%(n-m+1). 即,f代表,进行到剩余n-m+1个人时,该人的编号为k-1(以0为起点)。
然后递推出其初始编号。利用公式f(n,m) = (f(n-1,m)+m)%n,只不过起始条件有所改变。答案+1即可。
时间复杂度为O(m)。
但注意到,在这道题中,m<=1e18.
如果m<k,m<=2e6,可直接暴力;但如果m>k,m<=1e18,要寻找压缩这个递推过程的思路。
注意到,当m>k,此时有k<=2e6.此时,f(n-m+1,m) = (k-1)%(n-m+1)可能远小于n-m+2~n。
即,可能起始有一段递推过程,取模没有意义。
如此一来,就可以直接去掉这一段的取模操作,这一段的加法就可以被压缩为乘法。
为什么压缩这个过程就可以使得时间复杂度变安全了呢?参考
假若起始这一段可以被忽略取模的过程终止于第t次。
第0次:pos = (k-1)%(n-m+1)
第1次:pos2 = (pos+k)%(n-m+2)
第2次:pos3 = (pos+2k)%(n-m+3)
…
第t次:post = (pos+tk)%(n-m+1+t)
如果pos+t*k<n-m+t+1,这整个过程都不需要取模。因为左边的增幅大于右边。往前走更不可能需要取模。
此时t<(n-m+1-pos)/(k-1)。t次加法可以被合并成1次乘法。
到第t+1次,需要取模,取模之后,又有可能遇到可以压缩的状况。
最差情况:n=m=1e18,k=2e6、此时t = tmin -> 0.最多需要操作数 = 55875734
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
char ch = getchar();int x=0,f=1;while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();return x*f;
}
int main(){
int t = read(),cas = 0;
while(t--){
ll n,m,k;scanf("%lld%lld%lld",&n,&m,&k);
ll ans;
if(k==1) ans = m-1;
else if(m<=k){
ans = (k-1)%(n-m+1);
for(ll i=n-m+2;i<=n;++i) ans = (ans+k)%i;
}else{
ans = (k-1)%(n-m+1);
ll cur = n-m+1;
while(1){
ll tt = (cur-ans)/(k-1);
if(tt*(k-1)==cur-ans) --tt;
tt = min(tt,n-cur);
ans += k*tt;
cur += tt;
ans=ans%cur;
if(cur==n) break;
++cur;
ans=(ans+k)%cur;
if(cur==n) break;
}
}
printf("Case #%d: %lld\n",++cas,ans+1);
}
}