2022“杭电杯”中国大学生算法设计超级联赛(2)个人题解
时间:2022-12-01 16:00:01
不知道为什么 1001 一直 T,可能是现学树剖析的板子常数大。但是本地跑非随机数据只用了 0.58s.
发现查询复杂度不对,犯了低级错误。
1002 C to Python
签到。
1007 Snatch Groceries
很长的阅读理解废话,跳过发现是区间排序,也算签到。
废话骗了很多人
#include #define int long long const int maxn = 1e5 10; using namespace std; struct node {
int l, r; bool operator < (const node &x) const {
if (l == x.l) return r < x.r; return l < x.l; } }a[maxn]; signed main() {
ios::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) {
int n; cin >> n; for (int i = 1; i <= n; i) {
cin >> a[i].l >> a[i].r;
}
a[n + 1].l = 1e9 + 10;
sort(a + 1, a + 1 + n);
int cnt = 0;
for (int i = 1; i <= n; ++i) {
if (a[i].r >= a[i + 1].l) {
break;
}
cnt++;
}
cout << cnt << endl;
}
}
1012 Luxury cruise ship
看上去是入门的 dp 问题,选纸币。但是纸币值是 7 , 31 , 365 7,31,365 7,31,365,数据范围到了 1 e 18 1e18 1e18.
发现 365 = 5 × 31 + 30 × 7 365=5×31+30×7 365=5×31+30×7, 所以优先用 365 365 365 肯定是优于其他情况的。
所以先对 n n n 取模到 [ 0 , 365 ) [0,365) [0,365),然后再作 dp 即可,转移方程 f [ i ] = m i n ( f [ i − 7 ] , f [ i − 31 ] ) + 1 f[i]=min(f[i-7],f[i-31])+1 f[i]=min(f[i−7],f[i−31])+1.
实际上暴力一点,直接对前 7 × 31 × 365 = 79205 7×31×365=79205 7×31×365=79205 个数据预处理也是可以的,完全可接受。想太复杂了。
#include
using namespace std;
using ll = long long;
int a[3] = {
7, 31, 365};
int nums[1010];
int main()
{
memset(nums, 0x3f, sizeof(nums));
nums[0] = 0;
for (int i = 0; i < 3; i++)
for (int j = a[i]; j <= 1000; j++)
if (nums[j - a[i]] != 0x3f3f3f3f)
nums[j] = min(nums[j], nums[j - a[i]] + 1);
int t;
cin >> t;
while (t--)
{
ll x;
cin >> x;
if (x < 365)
{
if (nums[x] != 0x3f3f3f3f)
cout << nums[x] << endl;
else
cout << -1 << endl;
}
else
{
ll p = x / 365ll;
ll q = x % 365ll;
if (nums[q] != 0x3f3f3f3f)
cout << p + nums[q] << endl;
else
cout << p - 1 + nums[q + 365] << endl;
}
}
}
(队友的代码)
1009 ShuanQ
观察式子 x = P y % M , y = Q x % M x=Py\ \%\ M,y=Qx\ \% M x=Py % M,y=Qx %M.
其实不用观察
首先由条件 P Q % M = 1 PQ\ \%\ M = 1 PQ % M=1 可知 ( P Q − 1 ) % M = 0 (PQ-1)\ \%\ M=0 (PQ−1) % M=0. 即有
P Q = k M , k = 1 , 2 , . . . PQ=kM,k=1,2,... PQ=kM,k=1,2,...
又由最前面的式子知道, k k k 不可能小于 max { P , Q } \max\{P,Q\} max{ P,Q} 的,而且一定是一个质因子。
知道是质因子就好做了,找到一个最大的满足条件的就行。
实际上也很容易证明这样的 k k k 只有一个。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
int prime[maxn],cnt=0;
int vis[maxn];
int mnprime[maxn];
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!vis[i])prime[cnt++]=i;
for(int j=0;i*prime[j]<=n;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main(){
int tt;
get_prime(2000000);
scanf("%d",&tt);
while(tt--){
ll p,q,e;
cin>>p>>q>>e;
ll xx=p*q-1;
vector<long long>v;
v.clear();
for(int i=0;i<cnt;i++){
if(xx%prime[i]==0){
if(prime[i]>max(p,q)){
v.push_back(prime[i]);
}
while(xx%prime[i]==0)xx/=prime[i];
}
}
if(xx!=1)v.push_back(xx);
if(v.size()==0){
printf("shuanQ\n");
}
else{
printf("%lld\n",e*q%v[0]);
}
}
}
(还是队友写的,数论爷 orz)
1001 Static Query on Tree
题意:
给一个树,然后给 q q q 次询问,每次询问给三个节集 A , B , C A,B,C A,B,C,问有多少个点同时满足以下条件:
- 点在 A A A 中某一点到根节点 1 1 1 的简单路径上
- 点在 B B B 中某一点到根节点 1 1 1 的简单路径上
- 点到根节点的简单路径上存在 C C C 中某一点
数据保证 ∑ ( A + B + C ) < 2 e 5 \sum(A+B+C)<2e5 ∑(A+B+C)<2e5.
比赛的时候树剖调出来了,但是一直 TLE. 赛后继续 T.
自己的解法是,将树重链剖分,然后可以在 log \log log 时间内给 C C C 的每个节点的子树打上标记,总共花 O ( C log n ) O(C\log n) O(Clogn)。然后再给 A , B A,B A,B 每个点到根节点上的各点打上标记。由于重链剖分的性质,这个操作最多也就是 O ( ( A + B ) log 2 n ) O((A+B)\log^2n) O((A+B)log2n) 的。最后再询问整个树中哪些点被打上了三个标记, O ( log n ) O(\log n) O(logn).
总复杂度 O ( ( n + C + ( A + B ) log n ) log n ) O((n+C+(A+B)\log n)\log n) O((n+C+(A+B)logn)logn),我觉得不应该 TLE((( 事后看题解发现官方题解也用了这个方法,和我一模一样。教教我怎么写出常数小的树剖 qwq.
上面一段话完全是没有意识到自己怎么错了。
请看我的查询:
int exist(int k, int l, int r) {
if (a[k].l > r || a[k].r < l) return 0;
if (a[k].l >= l && a[k].r <= r) {
if (a[k].v1 == a[k].r - a[k].l + 1 && a[k].v2 == a[k].r - a[k].l + 1 && a[k].r - a[k].l + 1 == a[k].v3)
return a[k].r - a[k].l + 1;
}
if (a[k].l == a[k].r) return 0;
pushdown(k);
return (exist(lson, l, r) + exist(rson, l, r));
}
很明显即使到了范围内也没有强制 return
,相当于没有用懒标记, TLE 是很简单的事。
修改查询?Query 子树中有多少个点满足某个条件.
比如这里我们要的条件是满足同时具有 A , B , C A,B,C A,B,C 三个标记,那么我存的时候换一种形式,三个变量分别存:被 A A A 标记的点,被 A B AB AB 标记的点,被 A B C ABC ABC 标记的点。那么我首先用集合 A A A 更新,然后用 B B B 更新,更新的时候 A B AB AB 标记个数 = 区间内 A A A 的个数(而不是区间长度)× 下传的值,最后用 C C C 更新, A B C ABC ABC 标记个数 = 区间内 A B AB AB 标记个数 ×下传值.
改完就过了
#include
#define lson k << 1
#define rson k << 1 | 1
#define int long long
const int maxn = 2e5 + 10;
using namespace std;
int fa[maxn], dep[maxn], siz[maxn], son[maxn], top[maxn], dfn[maxn];
int cnt;
int n, q;
int ce;
vector<int> e[maxn];
void poufen1(int o, int f, int d) {
fa[o] = f;
dep[o] = d;
siz[o] = 1;
for (int i = 0; i < e[o].size(); ++i) {
int x = e[o][i];
if (x == f) continue;
poufen1(x, o, d + 1);
siz[o] += siz[x];
if (siz[x] > siz[son[o]]) son[o