A. City
链接:https://codeforces.com/gym/102471/problem/A
题意:给定 n × m n\times m n×m 的网格图,有 ( n + 1 ) × ( m + 1 ) (n+1)\times (m+1) (n+1)×(m+1) 个点,问有多少条线段满足两个端点在网格点上,且中点也在网格点上
思路:竖的和横的比较好算,斜着的只需要偶数偶数匹配一下就好了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,m;
int main()
{
scanf("%d%d",&n,&m);
ll ans=0;
for(int len=2; len<=n; len+=2)
ans+=(n-len+1)*(m+1);
for(int len=2; len<=m; len+=2)
ans+=(m-len+1)*(n+1);
for(int len1=2; len1<=n; len1+=2)
for(int len2=2; len2<=m; len2+=2)
ans+=(n-len1+1)*(m-len2+1)*2;
printf("%lld\n",ans);
return 0;
}
E. Flow
链接:https://codeforces.com/gym/102471/problem/E
题意:给定 n 个点 m 条边的有向图,从 1 到 n 会形成 k 条等长的路径,每条边上都有容量。现在可以将其中一条边容量 -1 ,另一条边的容量 + 1 。问至少操作几次可以使从 1 到 n 流过的流量最大
思路:
- 第一眼看到:感觉每条边的流量应该是:总流量平均到每一条边 = t o t m \frac {tot}{m} mtot,但其实这样是错的,多余的部分,依然可以在集中在一条路径上。一条路径的边数为: c n t = m k cnt = \frac mk cnt=km ,那么 k 条路径合在一起达到的平均值应该是: t o t c n t \frac {tot}{cnt} cnttot,这个才是最终的最大流量。
- 那么就可以将 k 条路径当成一条来看。把每条路径的容量,从小到大排序,然后把 k 条路径合在一起。此时,平均流量和总容量的差值,就是需要操作的次数。
- 这里我们无需关心,这个操作是怎么实现的,只需要关注当前状态和最终状态之间的差值即可
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n,m;
vector<pair<int,int> > e[maxn];
vector<int> we[maxn];
int main()
{
scanf("%d%d",&n,&m);
ll tot=0;
for(int i=1;i<=m;++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[u].push_back({v,w});
tot+=w;
}
int cnt=e[1].size();
for(int i=0;i<cnt;++i)
{
auto x=e[1][i];
int u=x.fi;
int w=x.se;
we[i].push_back(w);
while(u!=n)
{
w=e[u][0].se;
u=e[u][0].fi;
we[i].push_back(w);
}
}
for(int i=0;i<cnt;++i) sort(we[i].begin(),we[i].end());
ll ans=0;
ll avg=tot/we[0].size();
for(int i=0;i<we[0].size();++i)
{
ll sum=0;
for(int j=0;j<cnt;++j) sum+=we[j][i];
ans+=max(0ll,avg-sum);
}
printf("%lld\n",ans);
return 0;
}
M. Value
链接:https://codeforces.com/gym/102471/problem/M
题意:设 A 是 { 1 , 2 , … , n } \{1,2,\dots,n\} {1,2,…,n} 的一个子集。初始分数为 0 ,对每一个 i ∈ A i \in A i∈A ,增加 a i a_i ai 的分数,对于任意二元组 { i , j } \{i,j\} {i,j} 满足 i k = j i^k=j ik=j ( k > 1 ) (k>1) (k>1),则减去 b j b_j bj 。问怎样选择可以使得集合 A 的分数最大。这个最大分数是多少。 ( 1 ≤ n ≤ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 9 ) (1\le n \le 10^5,1\le a_i,b_i\le 10^9) (1≤n≤105,1≤ai,bi≤109)
思路:
- 根据幂可以这样分开来看: i , i 2 , i 3 , … i k i , i^2,i^3,\dots i^k i,i2,i3,…ik ,然后需要选择一个子集,使得获得的分数最大。
- 从后往前贪心:假设选择了 i 8 i^8 i8 ,当遇到 i 4 i^4 i4 的时候,发现 b[i^8] 特别大。此时,是选择保留: i 8 i^8 i8 还是 i 4 i^4 i4 ,我们是无法判断的。
- 不能贪心,不能DP。没有最优子结构,然后会产生后效性。
- 所以只能最暴力的方式了:直接枚举子集
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],b[maxn];
int visit[maxn];
ll solve(ll x)
{
ll k=x;
vector<int> vec;
while(k<=n)
{
visit[k]=1;
vec.push_back(k);
k*=x;
}
int cnt=vec.size();
ll ans=0;
for(int i=0; i<(1<<cnt); ++i)
{
vector<int> cur;
for(int j=0; j<cnt; ++j)
if(i>>j&1) cur.push_back(vec[j]);
ll res=0;
int m=cur.size();
for(int i=0; i<m; ++i)
{
res+=a[cur[i]];
for(int j=i+1; j<m; ++j)
{
ll tmp1=cur[i],tmp2=cur[j];
while(tmp1<tmp2) tmp1*=cur[i];
if(tmp1==tmp2) res-=b[cur[j]];
}
}
ans=max(ans,res);
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
for(int i=1; i<=n; ++i) scanf("%d",&b[i]);
ll ans=a[1];
for(int i=2; i<=n; ++i)
if(!visit[i]) ans+=solve(i);
printf("%lld",ans);
return 0;
}