1917题目&题解
CF660D Number of Parallelograms
题意
题目描述
给定平面上\(n\)个点,问用这些点能组成多少平行四边形(多个平行四边形可以共用点,保证任意三点不共线)
数据范围
\(1 \leq n \leq 2000\) , \(0\leq x_i,y_i \leq 10^{9}\)
题解
平四对角线互相平分,所以 \(n^2\) 求出两两之间的线段中点,如果有 \(k\) 条线段的中点在 \((x,y)\),那么说明存在 \(C_k^2\) 个平四。
代码
#include<bits/stdc++.h>
using namespace std;
int n,ans;
map<pair<int,int>,int>mp;
int x[2010],y[2010];
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
mp[make_pair(x[i]+x[j],y[i]+y[j])]++;
for(map<pair<int,int>,int>::iterator it=mp.begin();it!=mp.end();it++){
int t=it->second;
ans+=t*(t-1)/2;
}
cout<<ans<<endl;
return 0;
}
CF682D Alyona and Strings
题意
题目描述
给定两个字符串 \(s\) 和 \(t\),长度分别为 \(n\) 和 \(m\)。要求找出 \(k\) 个不相交的非空子串 \(p_1,p_2,...,p_k\),满足:
- 这些子串在 \(s\) 和 \(t\) 中按相同顺序出现
- 子串在 \(s\) 和 \(t\) 中均不相交(不重叠)
- 最大化这些子串的长度之和
数据范围
- 字符串长度:\(1 \leq n,m \leq 1000\)
- 子串数量 \(k\):\(1 \leq k \leq 10\)
- 字符集:小写字母
题解
观察到 \(n,m\) 只有 \(1000\),考虑 \(nm\) dp。
设 \(dp[i][j][k][0/1]\) 表示字符串 \(s\) 的前 \(i\) 位与字符串 \(t\) 的前 \(j\) 位,一共截断了 \(k\) 次,以及当前位选或不选 (\(0/1\)).
转移显然(见代码)。
注意,这么 dp 可能导致 MLE,可以把 int 改成 short 或者用滚动数组优化(我用了前者,比较懒)。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,p,ans;
char a[2010],b[2010];
short dp[2010][2010][11][2];
int main()
{
cin>>n>>m>>p>>(a+1)>>(b+1);
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=1;k<=p;k++)
dp[i][j][k][0]=dp[i][j][k][1]=-1e4;
for(int i=1;i<=n;i++)
{
// cout<<i<<endl;
for(int j=1;j<=m;j++)
{
// cout<<"=>"<<j<<endl;
for(int k=0;k<=p;k++)
{
dp[i][j][k][0]=max(dp[i-1][j-1][k][0],max(dp[i-1][j][k][0],dp[i][j-1][k][0]));
dp[i][j][k][0]=max(dp[i][j][k][0],max(dp[i-1][j-1][k][1],max(dp[i-1][j][k][1],dp[i][j-1][k][1])));
if(a[i]==b[j] && k) dp[i][j][k][1]=max((int)dp[i][j][k][1],max(dp[i-1][j-1][k-1][0],dp[i-1][j-1][k][1])+1);
// cout<<dp[i][j][k][0]<<" "<<dp[i][j][k][1]<<endl;
}
// cout<<endl;
}
// cout<<endl;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<=p;k++)
{
if(k==p) ans=max(ans,(int)max(dp[i][j][k][0],dp[i][j][k][1]));
else
{
if(dp[i][j][k][0]>=p) ans=max(ans,(int)dp[i][j][k][0]);
if(dp[i][j][k][1]>=p) ans=max(ans,(int)dp[i][j][k][1]);
}
}
cout<<ans;
return 0;
}
CF685B Kay and Snowflake
题意
题目大意
给定一棵以 \(1\) 为根、包含 \(n\) 个节点的树,进行 \(q\) 次查询。每次查询给出一个节点 \(v_i\),要求找出以 \(v_i\) 为根的子树的重心。重心的定义是:删除该节点后,剩余所有连通块的大小均不超过原子树大小的一半。
数据范围
- 节点数 \(n\) 和查询次数 \(q\):\(2 \leq n, q \leq 3 \times 10^5\)
- 树的输入方式:第 \(i\) 个节点(\(2 \leq i \leq n\))的父节点 \(p_i\) 给出,保证构成合法树结构
- 保证每个查询的子树至少存在一个重心
题解
观察到,如果 \(u\) 是 \(v\) 的父亲,那么 \(u\) 子树的重心的深度一定不大于 \(v\) 子树的重心的深度。
据此贪心即可,对于每个节点,取所有儿子中的最优解,因为每个点只会被访问一次,所以时间复杂度 \(O(n)\).
代码
#include<bits/stdc++.h>
using namespace std;
const int Maxn=3e5+10;
int n,q;
int mx[Maxn],ans[Maxn];
vector<int>edge[Maxn];
int sz[Maxn],fa[Maxn];
void dfs(int u)
{
sz[u]=1;
for(int i=0;i<edge[u].size();i++)
{
int v=edge[u][i];
if(v==fa[u]) continue;
dfs(v);
sz[u]+=sz[v];
mx[u]=max(mx[u],sz[v]);
}
ans[u]=u;
for(int i=0;i<edge[u].size();i++)
{
int v=edge[u][i];
if(v==fa[u]) continue;
v=ans[v];
while(fa[v]!=u && max(mx[v],sz[u]-sz[v])>=max(mx[fa[v]],sz[u]-sz[fa[v]])) v=fa[v];
if(max(mx[v],sz[u]-sz[v])<max(mx[ans[u]],sz[u]-sz[ans[u]])) ans[u]=v;
}
}
int main()
{
cin>>n>>q;
for(int i=2;i<=n;i++)
{
int u;
cin>>u; fa[i]=u;
edge[u].push_back(i);
edge[i].push_back(u);
}
dfs(1);
while(q--)
{
int u;
cin>>u;
cout<<ans[u]<<endl;
}
}
CF687C The Values You Can Make
题意
给定 \(n\) 个硬币,面值分别为 \(c_1, c_2, \ldots, c_n\),以及一个目标金额 \(k\)。请找出所有满足以下条件的 \(x\):
- 存在一个硬币子集,其和恰好为 \(k\);
- 在该子集中,存在另一个子集,其和恰好为 \(x\)。
请输出所有可能的 \(x\) 值(升序排列),包括 \(0\) 和 \(k\)。
\(1\leq c_i,n,k \leq 500\).
题解
考虑背包dp,\(dp_{i,j,k}\) 表示前 \(i\) 个物品,总价值为 \(j\),能否凑出面值为 \(k\)。
很明显,\(dp_{i,j}\) 可以从 \(dp_{i,j-a_i}\) 转移,也可以从 \(dp_{i-1,j}\) 转移。
考虑对于新选择的 \(a_i\),如果 \(dp_{i,j-a[i],k-a[i]}\) 为真,那么 \(dp_{i,j,k}\) 为真。
代码
#include<bits/stdc++.h>
// #define int long long
using namespace std;
int n,k,cnt;
int a[510],ans[510];
bool dp[510][510][510];
signed main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=0;i<=n;i++) dp[i][0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
for(int l=0;l<=j;l++)
{
if(j>=a[i] && l>=a[i]) dp[i][j][l]|=dp[i-1][j-a[i]][l-a[i]];
if(j>=a[i]) dp[i][j][l]|=dp[i-1][j-a[i]][l];
dp[i][j][l]|=dp[i-1][j][l];
}
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<=500;j++)
ans[j]|=dp[i][k][j];
for(int i=0;i<=500;i++) if(ans[i]) cnt++;
cout<<cnt<<endl;
for(int i=0;i<=500;i++) if(ans[i]) cout<<i<<" ";
cout<<endl;
return 0;
}
CF691E Xor-sequences
题意
给定 \(n\) 个整数 \(a_1, a_2, ..., a_n\),定义长度为 \(k\) 的序列 \(x_1, x_2, ..., x_k\) 为“xor-sequence”,当且仅当满足:
- 对于每一对相邻元素 \(x_i, x_{i+1}\),\(x_i \oplus x_{i+1}\) 的二进制表示中 1 的个数是 3 的倍数;
- 序列中的每个 \(x_i\) 都是从给定的 \(a\) 数组中选取的(可以重复选取,但视为不同元素)。
问长度为 \(k\) 的“xor-sequence”有多少种,答案对 \(10^9+7\) 取模。
\(1\leq n \leq 100\),\(1\leq k\leq 10^{18}\)。
题解
考虑建图,若 \(u,v\) 亦或起来满足第一个条件,那么将其连边,此时问题变为了:在途中有多少条长度为 \(k\) 的路径。
模板。
仿照 floyd,设 \(f_t[i][j]\) 表示以 \(i\) 为起点,\(j\) 为终点的长度为 \(t\) 的路径数量。
转移显然:
发现是矩阵乘法的形式,故用矩阵快速幂解决。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int n,k,ans;
int a[110];
struct MAT
{
int n,m;
int a[110][110];
MAT():n(0),m(0) {memset(a,0,sizeof(a));}
}f;
MAT operator*(MAT x,MAT y)
{
MAT re;
re.n=x.n; re.m=y.m;
for(int i=1;i<=re.n;i++)
{
for(int j=1;j<=re.m;j++)
{
for(int k=1;k<=x.m;k++)
{
re.a[i][j]+=x.a[i][k]*y.a[k][j]%mod;
if(re.a[i][j]>=mod) re.a[i][j]-=mod;
}
}
}
return re;
}
void output(MAT x)
{
for(int i=1;i<=x.n;i++)
{
for(int j=1;j<=x.m;j++) cout<<x.a[i][j]<<" ";
cout<<endl;
}
cout<<endl;
}
MAT ksm(MAT x,int b)
{
if(b==0) return x;
if(b==1) return x;
MAT re=ksm(x,b>>1);
re=re*re;
// output(re);
if(b&1) re=re*x;
return re;
}
bool check(int x)
{
int cnt=0;
while(x)
{
cnt++;
x-=(x&-x);
}
return cnt%3==0;
}
signed main()
{
cin>>n>>k; f.n=f.m=n;
if(k==1)
{
cout<<n<<endl;
return 0;
}
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(check(a[i]^a[j])) f.a[i][j]=1;
// output(f);
f=ksm(f,k-1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
ans+=f.a[i][j];
if(ans>=mod) ans-=mod;
}
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号