7a684047-65b5-481c-8550-b8712baed8d0
去年大一缺乏体验,今年比赛前心血来潮又V了一把,彻底成为竞赛败犬。。。。
K.连通最小乘积
签到,贪心搞一下,根据乘法结合分配律优化一下,正数负数相互结合就行。
vector<ll> fu,zheng;
void solve()
{
int n;
cin >> n;
for(int i=1;i<=n;i++){
int t;cin>>t;
if(t<=0) fu.push_back(t);
else zheng.push_back(t);
}
sort(zheng.begin(),zheng.end());
sort(fu.begin(),fu.end());
ll ans=0;
int ff=fu.size();
int zz=zheng.size();
if(fu.size()==0){
for(int i=1;i<zheng.size();i++){
ans+=zheng[0]*zheng[i];
}
}
else if(zheng.size()==0){
for(int i=fu.size()-2;i>=0;i--){
ans+=fu[ff-1]*fu[i];
}
}
else{
ll tt=0;
ans=0;
for(int i=0;i<zz;i++){
tt+=zheng[i];
}
for(int i=0;i<ff;i++){
ans+=tt*fu[i];
}
}
cout<<ans;
}
}
L.行星探索
也算个签到吧,很裸的二维前缀和,队友很快就A了。膜%%%%
#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 2e6 + 7;
const int mod = 998244353;
int s[1010][1010][4];
char p[1010][1010];
void solve()
{
int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
char x;
cin >> x;
if (x == 'C')
s[i][j][1] = 1;
if (x == 'M')
s[i][j][2] = 1;
if (x == 'F')
s[i][j][3] = 1;
}
for (int p = 1; p <= 3; p++)
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j][p] += s[i - 1][j][p] + s[i][j - 1][p] - s[i - 1][j - 1][p];
}
while (k--)
{
int x, y, xx, yy;
cin >> x >> y >> xx >> yy;
for (int p = 1; p <= 3; p++)
cout << s[xx][yy][p] - s[x - 1][yy][p] - s[xx][y - 1][p] + s[x - 1][y - 1][p] << " ";
cout << "\n";
}
}
M.二手物品回收
很一眼的全裸多重背包题,据说因为数据水很多人贪心过(,这里把每个商家看成一组,每组有自己的物品,可以选一些带走,背包容量为k,每次花费体积为1。需要特别注意的是需要严格选择k个,即使亏钱(crazy).
复杂度分析的话应该是O(m(k + n))。因为物品总数只有n个。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 1e3 + 10;
const int mod = 998244353;
ll dp[N][N];
int n, m, k;
vector<int> e[N];
ll c[N];
void solve()
{
cin >> n >> m >> k;
for (int i = 1; i <= m; i++)
{
cin >> c[i];
}
for (int i = 1; i <= n; i++)
{
int a, b;
cin >> a >> b;
e[b].push_back(a);//统计每组数量
}
for(int i=0;i<=m;i++){
for(int j=0;j<=k;j++){
dp[i][j] = -inf;//初始化为负数
}
}
for (int i = 1; i <= m; i++)
{
sort(e[i].rbegin(), e[i].rend());
}
dp[0][0] = 0;
for (int i = 1; i <= m; i++)
{
for (int j = k; j >= 0; j--)
{
ll sum = 0;
dp[i][j] = dp[i - 1][j];
for (int p = 0; p < e[i].size(); p++)
{
sum += e[i][p];
if (p + 1 <= j)
{
dp[i][j] = max(dp[i][j], dp[i - 1][j - p - 1] + sum - c[i]);
}
}
}
}
cout << dp[m][k] << '\n';
}
C.结对编程
也算个签到吧,没模数提示有点明显,找到规律就好了,队友又速A了%%%%
#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int N = 1e3 + 10;
const int mod = 998244353;
void solve()
{
int n;
cin >> n;
map<int, int> mp;
for (int i = 2; i <= n; i++)
{
int x;
cin >> x;
mp[x]++;
}
ll ans = 0;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
ans += x;
ans -= 1ll * x * mp[i];
}
cout << ans << endl;
}
以上就是23年省赛最签到的几个题了,仍记得全场坐牢的场景(惨。
G.异或解密
比较诈骗的一个题,玄学复杂度。
考虑原题式子可以左移可化为 Xi ^ a = Yi 。拆解成二进制可以理解为对于60位中的每一位,都可以使这一位置为 0 或 1 ,贡献与原来数字对应位数为1个数有关。
比如第j位有p个数包含1,如果a第j位为1,则贡献为 (1<<j) * (n - p),为0时则 *p。那么我们可以考虑爆搜,对于每一位分别置0/1时观察答案,时间复杂度为 O(2的60次方) (雾。
考虑剪枝,一个比较明显的剪枝是,当前位置0/1后产生的贡献大于可以到达的最大值,则不可以。另一个比较难观察的是,如果当前位后面所有位置为1时是能得到的最大值,如果这个值比要达到的值还要小,就证明不可能。
到此即可通过本题。
额外注意的一点是过程中可能会爆 int64.
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
#define ll __int128_t
const int N = 1e3 + 10;
const int mod = 998244353;
ll d[60];
long long n, S;
long long ans = 1e18;
bool dfs(ll now, ll p, ll res)
{
if (p == -1)
{
if (now == 0)
{
ans = res;
return true;
}
return false;
}
ll k = d[p]*(1ll << p);
if (k <= now&&(now - k) / n<=__int128_t((1ll << p) - 1))
{
if (dfs(now - k, p - 1, res))
{
return true;
}
}
k = (1ll << p) * (n - d[p]);
if (k <= now&&(now - k) / n<=__int128_t((1ll << p) - 1))
{
return dfs(now - k, p - 1, res + (1ll << p));
}
return false;
}
void solve()
{
cin >> n >> S;
for (int i = 0; i < n; i++)
{
long long x;
cin >> x;
for (int j = 0; j < 60; j++)
{
if (x >> j & 1)
{
d[j]++;
}
}
}
dfs(S, 59, 0);
if (ans == 1e18)
{
cout << -1 << '\n';
}
else
{
cout << ans << '\n';
}
}
A.列车售货员难题
很好的一道题,使我汗流浃背。。。。
比较朴素的想法是,枚举所有区间计算答案,但是显然会超时。此时我们注意到m的大小只有100,我们对于m进行优化。考虑枚举区间左端点,对于这个左端点会产生的贡献,考虑从最右端减到最左端,每当丢失一种物品时会产生贡献,而丢失物品的次数不超过m次。我们可以预处理出dp[i][j]表示以i为左端点,j最后一次出现的位置(靠左)是哪里,之后我们可以在O(nm)的枚举中处理出答案。
到这这题已经差不多了,但是还有一个折磨我许久的问题是如何去重,因为这些区间产生的集合可能是重复的,一个比较经典的写法是使用__int128表示集合,每一位表示对应第i个有没有,最后使用map或set去重即可。我使用了一种哈希异或映射的方法通过本题。
#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using ull = unsigned long long;
const int N = 2e5 + 10;
const int mod = 998244353;
const ll os = 1331;
int f[N][110];
mt19937_64 rnd(98275314);
long long gen()
{
long long x = 0;
while(x == 0)
x = rnd();
return x;
}
void solve()
{
int n, m;
cin >> n >> m;
vector<vector<int>> a(n + 1);
vector<ll>mp(m + 1);
for(int i=1;i<=m;i++){
mp[i] = gen();//每一个都映射一种值
}
for (int i = 1; i <= n; i++)
{
int k;
cin >> k;
a[i].resize(k);
for (auto &x : a[i])
{
cin >> x;
}
}
for (int i = n; i >= 1; i--)
{
for (int j = 1; j <= m; j++)
{
f[i][j] = f[i + 1][j];
}
for (auto x : a[i])
{
f[i][x] = i;
}
}
set<ll> s;
for (int i = 1; i <= n; i++)
{
vector<pii> id;
ll ps = 0;
for (int j = 1; j <= m; j++)
{
if (f[i][j] == 0)
continue;
id.push_back({f[i][j], j});
}
sort(id.begin(), id.end());
for (int j = 0; j < id.size(); j++)
{
ps ^= mp[id[j].second];
}
if(ps)s.insert(ps);//非空
for (int j = id.size() - 1; j >= 0; j--)
{
ps ^= mp[id[j].second];
if (j > 0 && id[j].first == id[j - 1].first)
continue;
if (ps)
s.insert(ps);
}
}
cout << s.size() << '\n';
}
I.calc
很nice的一道题,使我思维旋转。
求方案数,考虑计数dp。但是一看数据范围1e7和7。很合理的想到状压dp+矩阵快速幂优化。
考虑如何设计状态,k的范围是[1,7],考虑连续的七个数,用二进制状态表示,根据输入处理出不合法的状态,可以得到1-7的答案,考虑向后递推。每向左移动一位,产生的新的状态判断是否合法,如果合法即可转移。再看对于左移加一,如果合法且上一个丢失的位置与这一位没有冲突,也可以转移,由此可设计出(1<<k) * (1<<k)的转移矩阵,直接转移即可。
#pragma GCC optimize(3, "Ofast")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
const int mod = 998244353;
struct Mat
{
int v[130][130] = {0};
int x, y;
Mat() {}
Mat(int _x, int _y) { x = _x, y = _y; }
// 清空
void zero()
{
memset(v, 0, sizeof(v));
x = y = 0;
}
// 单位矩阵化
void ones()
{
memset(v, 0, sizeof(v));
for (int i = 0; i <= x; i++)
{
v[i][i] = 1;
}
}
// 乘法
void Mul(Mat a, Mat b)
{
zero();
x = a.x, y = b.y;
int c = a.y;
for (int i = 0; i <= x; ++i)
{
for (int j = 0; j <= y; ++j)
{
for (int k = 0; k <= c; ++k)
{
v[i][j] += (long long)a.v[i][k] * b.v[k][j] % mod;
v[i][j] %= mod;
}
}
}
return;
}
// 打印
void show()
{
for (int i = 1; i <= x; i++)
{
for (int j = 1; j <= y; j++)
{
cout << v[i][j] << " ";
}
cout << '\n';
}
}
};
Mat operator*(const Mat &x, const Mat &y)
{
Mat res;
res.Mul(x, y);
return res;
}
Mat ksm(Mat a, long long b)
{
Mat x = a;
x.ones();
while (b)
{
if (b & 1)
x = x * a;
b >>= 1;
a = a * a;
}
return x;
}
void solve()
{
int k, n;
cin >> k >> n;
vector<int> s(8);
for (int i = 0; i < k; i++)
{
int x;
cin >> x;
s[x] = 1;
}
vector<int> f(1 << 7);
for (int i = 0; i < (1 << 7); i++)
{
vector<int> a;
for (int j = 0; j < 7; j++)
{
if (i >> j & 1)
{
a.push_back(j + 1);
}
}
int ok = 1;
for (int p = 0; p < a.size(); p++)
{
for (int q = p + 1; q < a.size(); q++)
{
if (s[a[q] - a[p]])
ok = 0;
}
}
f[i] = ok;
}//预处理合法位置
if (n <= 7)
{
int ans = 0;
for (int i = 0; i < (1 << n); i++)
{
ans += f[i];
}
cout << ans - 1<< '\n';
}
else
{
Mat a(1, (1 << 7) - 1);
for (int i = 0; i < (1 << 7); i++)
{
a.v[1][i] = f[i];
}
Mat op(127, 127);
for (int i = 0; i < (1 << 7); i++)//设计矩阵
{
int sp = i;
int ok = 0;
if (sp & (1 << 6))
ok = 1;
if (ok)
sp -= (1 << 6);
sp <<= 1;
op.v[i][sp] = 1;
op.v[i][sp | 1] = f[sp | 1] && !(s[7] && ok);//新旧位是否冲突
}
Mat res = a * ksm(op, n - 7);
ll ans = 0;
for(int i=0;i<(1<<7);i++){
ans += res.v[1][i];
ans %= mod;
}
cout << ans - 1<< '\n';//减去全0方案数
}
}
到这差不多结束了,但还是有点遗憾,再口胡两道已知的题目。
F.实验器材采购
23年赛时过了这个题,当时是学长写的,据说是一个带修莫队(雾
H.部落冲突
很裸的半平面交,因为23年赛时没学过用其它麻烦的方法没调出来而感到遗憾(败犬
。。。。不会LateX使我的题解难看。
7a684047-65b5-481c-8550-b8712baed8d0