A:做游戏
签到
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
ll a,b,c;ll x,y,z;
cin>>a>>b>>c>>x>>y>>z;
cout<<min(a,y)+min(b,z)+min(c,x)<<endl;
}
B:排数字
签到
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e5+5;
char a[maxn];
int main()
{
ll n;cin>>n;
ll c1=0,c6=0;
for(ll i=1;i<=n;i++) {cin>>a[i];if(a[i]-'0'==1) c1++;if(a[i]-'0'==6) c6++;}
if(c6&&c1&&c6>=c1+1) cout<<c1<<endl;
else if(c6&&c1&&c6<=c1) cout<<c6-1<<endl;
else cout<<"0"<<endl;
}
C:算概率
tag:数论,期望dp
Emmm,这题还是有点难搞的,主要是自己第一次接触期望dp,不知道这种取模的含义,这种取模比较另类吧(可能现在还一知半解的),我个人理解是这样字的,比如概率p=a/b,你就用一种类似哈希的思想(纯属口胡,不知道该咋称呼),转换成一个很大的数字q%mod,所以呢(1-p)就可转换成(mod+1-q)%mod了,是一种表示概率的转化思想。弄懂这个了然后再想这个dp问题,状态转移方程,i道题你对了j道的概率就是:
f[i][j]=f[i-1][j]*(1-p[i])+f[i-1][j-1]*p[i];
这个应该很容易懂吧,学过概率论就很清楚
当然啦,我刚刚说过一种类哈希思想的数字转化,p[i]在输入的时候已经转化过了,所以针对1-p[i]你还得亲自操刀转化,p[i]必然大于1的,所以转化应该是(mod+1-p[i])%mod。这样就比较清楚易懂了,这题主要是不知道类哈希转化…呜呜呜
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const ll N=2e3+5;
ll n,p[N],f[N][N];
int main()
{
cin>>n;
for(ll i=1;i<=n;i++) cin>>p[i];
f[0][0]=1;
for(ll i=1;i<=n;i++){
f[i][0]=f[i-1][0]*(mod+1-p[i])%mod;
for(ll j=1;j<=i;j++){
f[i][j]=(f[i-1][j]*(mod+1-p[i])+f[i-1][j-1]*p[i])%mod;
}
}
for(ll i=0;i<=n;i++) cout<<f[n][i]<<" ";
return 0;
}
D:数三角
tag:计算几何
这个题出的很好,因为我本来用余弦定理来做然后成功的被卡了eps(wa了20发长记性了呜呜呜呜),这道题解法很多,比如比较边的平方和啦(我最后用这种方法过的),但是我觉得大部分人可能没想那么多,就直接用的余弦定理来过,Emmm这里我要提一下,如果double的话,不要和0比较,因为浮点数double是无限接近于0的,我们用eps 1e-8来表示
上这张图更清楚易懂的理解
附上余弦定理解法版本:
#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
struct node{
double x,y;
}a[502];
int main()
{
IO;int n;
scanf("%d",&n);
for(ll i=1;i<=n;i++){
scanf("%lf%lf",&a[i].x,&a[i].y);
}
int cnt=0;
for(int i=1;i<=n-2;i++){
for(int j=i+1;j<=n-1;j++){
for(int k=j+1;k<=n;k++){
double z1=sqrt((a[i].y-a[j].y)*(a[i].y-a[j].y)+(a[i].x-a[j].x)*(a[i].x-a[j].x));
double z2=sqrt((a[i].y-a[k].y)*(a[i].y-a[k].y)+(a[i].x-a[k].x)*(a[i].x-a[k].x));
double z3=sqrt((a[j].y-a[k].y)*(a[j].y-a[k].y)+(a[j].x-a[k].x)*(a[j].x-a[k].x));
double c3=(z1*z1+z2*z2-z3*z3)/(2*z1*z2);
double c2=(z1*z1+z3*z3-z2*z2)/(2*z1*z3);
double c1=(z3*z3+z2*z2-z1*z1)/(2*z2*z3);
if(z1+z2==z3||z2+z3==z1||z1+z3==z2) continue;
if(c1<-(1e-8)||c2<-(1e-8)||c3<-(1e-8)) cnt++;
}
}
}
printf("%d\n",cnt);
}
E:做计数
这个题很有意思
sqrt(i)+sqrt(j)=sqrt(k)
完全平方
i+j+2sqrt(ij)=k
要符合条件,需要i*j为完全平方数
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=2e5+5;
int main()
{
ll n;cin>>n;
ll ans=0;
ll m=sqrt(n);
for(ll i=1;i<=m;i++){
for(ll j=1;j<=i;j++){
if((i*i)%j==0){//判断因数对
ans++;
if((i*i)/j!=j) ans++;//如果2个因数不一样可以弄2次
}
}
}
cout<<ans<<endl;
}
F:拿物品
这个题和CHD第二次排位赛的一道题很有意思(论证方法和国王游戏一样),这道题我一开始想错了,以为上下都从大到小排序,然后取最大标记vis[id],因为我们要考虑问题的时候:自己要赚的多,但是也得想歪招让对方赚的少。所以呢就不是那么简单的贪心思想了。以下给了正解,这个方法很常用。
#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
struct node{
ll id,v1,v2;
}a[maxn];
bool cmp(node a,node b){
return a.v1+a.v2>b.v1+b.v2;
}
int main()
{
ll n;cin>>n;
for(ll i=1;i<=n;i++) {cin>>a[i].v1;a[i].id=i;}
for(ll i=1;i<=n;i++) {cin>>a[i].v2;}
sort(a+1,a+n+1,cmp);
vector<ll> aa;vector<ll> bb;
ll cnt=0;
for(ll i=1;i<=n;i++){
if(cnt%2==0) aa.push_back(a[i].id),cnt++;
else bb.push_back(a[i].id),cnt++;
}
for(ll i=0;i<aa.size();i++) {cout<<aa[i]<<" ";} cout<<endl;
for(ll i=0;i<bb.size();i++) {cout<<bb[i]<<" ";} cout<<endl;
}
/*
4
1 8 2 4
2 7 4 3
这个样例可以证明贪心取最大然后标记记录的做法是错误的
*/
G:判正误
签到
tag:快速幂取模
#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const ll maxn=2e5+5;
ll qp(ll a,ll b,ll c){
ll ans = 1;
ll base = a%c;
while(b){
if(b & 1) ans = (ans*base)%c;
base = (base*base)%c;
b >>= 1;
}
return ans;
}
int main()
{
ll q;cin>>q;
while(q--){
ll a,b,c,d,e,f,g;
cin>>a>>b>>c>>d>>e>>f>>g;
ll zy=qp(a,d,mod)+qp(b,e,mod)+qp(c,f,mod);
if(zy!=g) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
}
H:施魔法
tag:dp,dp的优化
这是一道非常好的dp问题(题解如下)
后来补题的时候,我写了一个TLE的代码,复杂度是O(n^2):
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 7;
int dp[N], pre, a[N], n, k;
int main()
{
cin>>n>>k;
for(ll i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
for(ll i=1;i<=n;i++) dp[i]=2e9;
for(ll i=k;i<=n;i++){
for(ll j=0;j<=i-k;j++){
dp[i]=min(dp[i],dp[j]+a[i]-a[j+1]);
}
}
cout<<dp[n]<<endl;
}
其实是跟题解里的式子一样的,第一个等号出来了,关键在于第二个等号,我们发现对于第二个循环枚举j来说,a[i]是多余的,可以提出来,然后剩下dp[j]-a[j+1]。
其实我们枚举第二个循环的目的就找这个最小的dp[j]-a[j+1],但这个式子只与当前的自变量有关,我们可以直接用一个pre变量在算的同时维护掉,省去这个第二重枚举。
就是dp递推转移式子的化简来实现dp复杂度的优化。
后来补一下AC的正确代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 7;
ll dp[N], pre, a[N], n, k;
int main()
{
cin>>n>>k;
for(ll i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
for(ll i=1;i<k;i++) dp[i]=2e9;
ll pre=-a[1];
for(ll i=k;i<=n;i++){
dp[i]=pre+a[i];
pre=min(pre,dp[i-k+1]-a[i-k+2]);
}
cout<<dp[n]<<endl;
}
I:建通道
这个题出的挺有意思的,我们把vi^vj看成一个数x吧,所以就是求x的lowbit。
所以我们应该想方设法的给x造1。那么我们需要找一个数位k,然后对m个去重后的数字遍历,该k位需要有0和1。那么有0的就可以和1匹配,得到1;有1的就可以和0匹配得到0.
这样的话该位置k就是1了,所以呢,即(1<<k)*(m-1)就行了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
ll v[maxn];
int main()
{
ll n;cin>>n;
for(ll i=0;i<n;i++) cin>>v[i];
sort(v,v+n);
ll m=unique(v,v+n)-v;//去重操作
if(m==1) cout<<"0"<<endl;
else{
for(ll i=0;i<=30;i++){
ll cnt0=0,cnt1=0;
for(ll j=0;j<m;j++){
if((v[j]&1<<i)==0) cnt0++;//记录0的个数
else cnt1++; //记录1的个数
}
if(cnt0&&cnt1){//如果该位置同时有1和0的话
cout<<(1<<i)*(m-1)<<endl;//记住是m-1条联通边哦
break;
}
}
}
return 0;
}
J:求函数
待补