前言:再一次OTL
JZOJ 3508 好元素
题目
求满足 a [ m ] + a [ n ] + a [ p ] = a [ i ] a[m]+a[n]+a[p]=a[i] a[m]+a[n]+a[p]=a[i]的i的个数
分析
移项后得到 a [ m ] + a [ n ] = a [ i ] − a [ p ] a[m]+a[n]=a[i]-a[p] a[m]+a[n]=a[i]−a[p],所以用 O ( n 2 ) O(n^2) O(n2)哈希存储,再用 O ( n 2 ) O(n^2) O(n2)找到答案。
代码
#include <cstdio>
#include <cctype>
#define p 19996001(抠门的哈希函数)
int n,a[5001],hash[p],ans;
inline int in(){
int ans=0,f=1; char c=getchar();
while (!isdigit(c)&&c!='-') c=getchar();
if (c=='-') c=getchar(),f=-f;
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans*f;
}
int locate(int x){//找哈希表的位置
int pos=(x%p+p)%p,i=0;
while (i<p&&hash[(pos+i)%p]!=p+1&&hash[(pos+i)%p]!=x) i++;
return (pos+i)%p;
}
signed main(){
freopen("good.in","r",stdin);
freopen("good.out","w",stdout);
n=in(); for (int i=0;i<p;i++) hash[i]=p+1;
for (int i=1;i<=n;i++){
a[i]=in(); bool flag=0; int x;
for (int j=1;j<i&&!flag;j++) if (hash[locate(a[i]-a[j])]==a[i]-a[j]) ans++,flag=1;//查询
if (i<n) for (int j=1;j<=i;j++) hash[locate(a[i]+a[j])]=a[i]+a[j];//补充哈希表
}
return !printf("%d",ans);
}
JZOJ 3509 倒霉的小C
题目
求 1 + ∑ i = 1 n g c d ( n , i ) 1+\sum_{i=1}^ngcd(n,i) 1+∑i=1ngcd(n,i)
分析
这个问题可以改变成 1 + ∑ d ∣ n d ∗ φ ( n / d ) , φ ( n ) 指 1 到 n 中 与 n 互 质 的 数 1+\sum_{d|n}d*\varphi(n/d),\varphi(n)指1到n中与n互质的数 1+∑d∣nd∗φ(n/d),φ(n)指1到n中与n互质的数,证明不会遗漏,不会重复比较简单,算出 φ \varphi φ说明了每个数只会出现一次,时间复杂度 O ( n 的 约 数 log n 的 约 数 ) O(n的约数\log n的约数) O(n的约数logn的约数)
代码
#include <cstdio>
using namespace std;
typedef long long ll;
ll ans,n;
ll phi(ll n){
ll ans=n;
for (ll i=2;i*i<=n;i++)
if (n%i==0){
ans=ans/i*(i-1);
while (n%i==0) n/=i;
}
if (n>1) ans=ans/n*(n-1);
return ans;
}
int main(){
freopen("beats.in","r",stdin);
freopen("beats.out","w",stdout);
scanf("%lld",&n); ans=1;
for (ll i=1;i*i<=n;i++)
if (n%i==0){
ans+=i*phi(n/i);
if (i*i!=n) ans+=n/i*phi(i);
}
return !printf("%lld",ans);
}
JZOJ 3510 最短路径
题目
在每个点经过一次的情况下从 A 走到 B,再回到 A(A点两次) 的最短路径。并且到B的路上横坐标要从小到大走, b 1 b_1 b1只能在到B的路上走, b 2 b_2 b2只能在回A的路上走,回A时横坐标要从大到小走。
分析
题目满足无后效性,所以是动态规划,两点的直线距离比较容易,但问题是如何dp
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示去B的路经过i,回A的路经过j的最短路径,显然得到(
k
=
m
a
x
(
i
,
j
)
+
1
k=max(i,j)+1
k=max(i,j)+1)
f
[
k
]
[
j
]
=
m
i
n
(
f
[
k
]
[
j
]
,
f
[
i
]
[
j
]
+
d
i
s
[
i
]
[
k
]
)
当
k
未
经
过
b
2
f[k][j]=min(f[k][j],f[i][j]+dis[i][k])当k未经过b2
f[k][j]=min(f[k][j],f[i][j]+dis[i][k])当k未经过b2
f
[
i
]
[
k
]
=
m
i
n
(
f
[
i
]
[
k
]
,
f
[
i
]
[
j
]
+
d
i
s
[
j
]
[
k
]
)
当
k
未
经
过
b
1
f[i][k]=min(f[i][k],f[i][j]+dis[j][k])当k未经过b1
f[i][k]=min(f[i][k],f[i][j]+dis[j][k])当k未经过b1
注意
i
=
j
和
f
[
n
]
[
n
]
i=j和f[n][n]
i=j和f[n][n]要特判
代码
#include <bits/stdc++.h>
#define N 1001
using namespace std;
double f[N][N],dis[N][N]; int n,b1,b2,x[N],y[N];
int in(){
int ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
int main(){
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
n=in(); b1=in(); b2=in();
for (int i=1;i<=n;i++){
x[i]=in(); y[i]=in();
for (int j=1;j<i;j++) dis[i][j]=dis[j][i]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));//求距离
for (int j=1;j<=n;j++) f[i][j]=2147483647;
}
f[1][1]=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
if (i==j&&i>1) continue;
int k=max(i,j)+1;//下一个点
if (k>n){//走到终点
if (i<n) f[n][n]=min(f[n][n],f[i][j]+dis[i][n]);
if (j<n) f[n][n]=min(f[n][n],f[i][j]+dis[j][n]);
}
else{
if (k!=b2+1) f[k][j]=min(f[k][j],f[i][j]+dis[i][k]);//没有经过b2
if (k!=b1+1) f[i][k]=min(f[i][k],f[i][j]+dis[j][k]);//没有经过b1
}
}
return !printf("%.2lf",f[n][n]);
}
JZOJ 3511 游戏节目
题目
队伍A,B,C有n个游戏节目,玩第i个游戏,分别可得A[i],B[i],C[i]的分数。从n个游戏节目里面挑选至少k个节目出来(被选中的节目不分次序),使得队伍A成为赢家。队伍A能成为赢家的条件是队伍A的总得分要比队伍B的总得分要高,同时也要比队伍C的总得分要高。求有多少种不同的选取方案。
分析
这道题直接求会TLE(要开long long),所以可以用总方案-不够k个节目的方案,而由于
k
≤
7
k\leq7
k≤7,所以比较容易深搜,那问题是怎样求总方案,可以双向搜索,把1n/2和n/2+1n中的所有可能存下来,离散后用树状数组维护,那具体如何排序
s
u
m
a
−
b
+
s
u
m
a
−
c
>
0
sum_{a-b}+sum_{a-c}>0
suma−b+suma−c>0,移项后得到
s
u
m
a
−
b
>
−
s
u
m
a
−
c
sum_{a-b}>-sum_{a-c}
suma−b>−suma−c,所以最后时间复杂度
O
(
2
17
l
o
g
2
17
+
∑
i
=
0
6
C
34
i
)
≈
O
(
1544488
+
1676116
)
=
O
(
3220604
)
≈
O
(
3.22
∗
1
0
6
)
O(2^{17}log2^{17}+\sum_{i=0}^{6} C_{34}^i)\approx O(1544488+1676116)=O(3220604)\approx O(3.22*10^6)
O(217log217+∑i=06C34i)≈O(1544488+1676116)=O(3220604)≈O(3.22∗106)
代码
#include <cstdio>
#include <cctype>
#include <algorithm>
#define rep(i,a,b) for (ll i=a;i<=b;i++)
using namespace std;
typedef long long ll; ll ab,ac,j=1,ans,ans1,num1,num2,t;
struct spd{ll ab,ix; bool mark;}k3[262145];
struct rec{ll ab,ac;}k1[131073],k2[131073];
int n,k,a[35],b[35],c[35],s[262145];
ll in(){
ll ans=0; char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=ans*10+c-48,c=getchar();
return ans;
}
void dfs(ll dep,ll now){
if (dep>=k) return; if (ab>0&&ac>0) ans++;
rep(i,now+1,n) ab+=a[i]-b[i],ac+=a[i]-c[i],dfs(dep+1,i),ab-=a[i]-b[i],ac-=a[i]-c[i];
}
void dfs1(ll now){k1[++num1]=(rec){ab,ac}; rep(i,now+1,n>>1) ab+=a[i]-b[i],ac+=a[i]-c[i],dfs1(i),ab-=a[i]-b[i],ac-=a[i]-c[i];}
void dfs2(ll now){k2[++num2]=(rec){ab,ac}; rep(i,now+1,n) ab+=a[i]-b[i],ac+=a[i]-c[i],dfs2(i),ab-=a[i]-b[i],ac-=a[i]-c[i];}
bool cmp1(spd x,spd y){return x.ab<y.ab;} bool cmp2(rec x,rec y){return x.ac<y.ac;} bool cmp3(rec x,rec y){return x.ac>y.ac;}
void add(ll x){while (x<=t) s[x]++,x+=-x&x;} ll answer(ll x){ll ans=0; while (x) ans+=s[x],x-=-x&x; return ans;}
int main(){
freopen("show.in","r",stdin);
freopen("show.out","w",stdout);
n=in(); k=in(); t=1;
rep(i,1,n) a[i]=in(); rep(i,1,n) b[i]=in(); rep(i,1,n) c[i]=in();
dfs(0,0); dfs1(0); dfs2(n>>1);//三次深搜
rep(i,1,num1) k3[i]=(spd){k1[i].ab,i,0};
rep(i,1,num2) k3[i+num1]=(spd){-k2[i].ab,i,1};
stable_sort(k3+1,k3+1+num1+num2,cmp1);//第一次快排
if (!k3[1].mark) k1[k3[1].ix].ab=1; else k2[k3[1].ix].ab=1;
rep(i,2,num1+num2){//离散
if (k3[i].ab!=k3[i-1].ab) t++;
if (!k3[i].mark) k1[k3[i].ix].ab=t; else k2[k3[i].ix].ab=t;
}
stable_sort(k1+1,k1+1+num1,cmp2); stable_sort(k2+1,k2+1+num2,cmp3);//第二次快排
rep(i,1,num1){
while (j<=num2&&k1[i].ac+k2[j].ac>0) add(k2[j].ab),j++;//树状数组维护
ans1+=answer(k1[i].ab-1);//找答案
}
return !printf("%lld",ans1-ans);//输出总方案-不合法的方案
}
后续
向出题人orz