Hello everyone!
Cybros, the competitive programming club of LNMIIT Jaipur is back again to invite you to participate in Enigma 2025 — Cybros LNMIIT. Enigma is the flagship event of Cybros LNMIIT conducted in the LNMIIT Jaipur's annual tech fest Plinth.
This will be an individual programming contest and the duration will be 2.5 hours and you will be given 6 problems to solve.
- Registration Form : Form link
- Contest Link : Enigma '25
- Date : January 26, 2025 at 14:00 IST (8:30 UTC)
The prize pool of the contest is INR 22000 split into two divisions of participants, online and onsite. You must fill the registration form if you want to be considered for the prizes.
Prize pool for onsite participants :
- INR 10000 (Winner)
- INR 7000 (First runner up)
- INR 3000 (Second runner up)
Prize pool for online participants (only applicable for Indian school/college students):
- INR 2000 (Winner)
- Goodies (Top 5 participants)
The problems have been authored and tested by synthborne, paramveer5404, Punpun018, AyushK22, Overlord99.
Cybros LNMIIT is also conducting more Competitive Programming events in their tech fest. You can check the rest of the events in the brochure which might interest you.
As a part of Plinth, we will also conduct IUPC (Inter University Programming Contest), which is an ICPC-like contest for teams of three people. This contest is a good practice for the real ICPC rounds with cash prizes.
You can register for rest of the events from our Instagram and receive future updates regarding the events.
UPD1 : Contest registrations are open!
UPD2 : The contest starts in 30 minutes. Do register.
UPD3 : Top 5 participants of the contest :
First solves :
- A : arnabmanna
- B : arnabmanna
- C : 1_2_3_4_5_9
- D : Banis
- E : kingmessi
- F : 1_2_3_4_5_9
UPD4 : We are sorry for the delayed editorial.
EDITORIAL
Author : AyushK22, synthborne
Lets see when the answer is $$$0$$$. If two consecutive non missing elements are not following the condition i.e. $$$a_{i + 1} \leq a_{i}$$$. If two elements one to the left of a missing position, lets call is $$$a$$$ and one to the right, lets call it $$$b$$$ are not following the condition i.e. $$$b \leq a + 1$$$.
In any other scenario, the answer always exists. We can place $$$b - a - 1$$$ elements at each missing position ($$$a$$$ and $$$b$$$ as defined above) and multiply all these values to get the answer.
#include<bits/stdc++.h>
#define int long long
using namespace std;
const char nl = '\n';
const char _ = ' ';
void fn(){
int n; cin>>n;
int arr[n];
int M = 1e9 + 7;
for(int i = 0; i < n; i++) cin>>arr[i];
for(int i = 1; i < n; i++){
if(arr[i] != -1 && arr[i - 1] != -1){
if(arr[i] <= arr[i - 1]){
cout<<0<<endl;
return;
}
}
}
int p = 1;
for(int i = 1; i < n - 1; i++){
if(arr[i] == -1){
int d = arr[i + 1] - arr[i - 1] - 1;
if(d <= 0){
cout<<0<<endl;
return;
}
p = (p * 1LL * d) % M;
}
}
cout<<p<<endl;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int T; cin >> T;
for (int t=1; t<=T; ++t)
fn();
return 0;
}
Author : roronoa_2z
B : 580057B - Bitwise Treasure Hunt
If a bit is unset it remains unset.
Notice when a new AND value is added to the set.
The AND Sequence, though infinite, involves a decreasing operation. The solution employs a greedy approach: if a continuous segment of ones is set in the binary representation of the last number added to the set, they all become unset simultaneously. Thus, our approach is simple: set all consecutive segments of ones from right to left to $$$0$$$, and each time, add the resulting number to the set. Given that $$$n \le 10^{18} $$$, we have at most $$$\mathcal{O}(60)$$$ operations per test case, as there are a maximum of $$$60$$$ bits.
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef unsigned long long ull ;
void solve()
{
int n ;
cin >> n ;
bitset<65> bt1(n);
set<int> possible ;
int previ= 0 ;
for(int i =0 ;i<=64; i++ )
{
if( bt1[i] == 0 )
{
for(int j = i-1 ; j>=previ ;j-- )
{
bt1[j] = 0 ;
}
ull t2= bt1.to_ullong() ;
possible.insert(t2);
previ = i;
}
}
cout << possible.size() << "\n";
for( auto x: possible)
{
cout << x << " " ;
}
cout << "\n" ;
}
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int tt;
cin >> tt;
while(tt--)
solve();
return 0;
}
Author : synthborne
C : 580057C - Permutation Colouring
At most $$$k$$$ operations is equivalent to exactly $$$k$$$ operations here.
Think about number of adjacent value pairs $$$a_i > a_{i + 1}$$$ in the permutations.
Lets rephrase the problem first. If we can colour a permutation in $$$k$$$ operations, we can always colour it in exactly $$$k$$$ operations. So, we will try to colour permutations in least number of operations possible. We will always select a maximal increasing subarray at any instance and colour it entirely. Now, the problem reduces down to finding minimum number of increasing subarrays we can split the permutation into, lets call it $$$x$$$ which can be coloured in $$$x$$$ operations.
We can see that if a permutation can be split into a minimum of $$$x$$$ increasing subarrays. It must have $$$x - 1$$$ number of adjacent value pairs where $$$a_i > a_{i + 1}$$$. Lets call these kinds of adjacent value pairs as "bad" pairs. So, we will try to find the number of permutations of length $$$n$$$ for each value of $$$j$$$ from $$$0$$$ to $$$k - 1$$$ where $$$j$$$ denotes the number of bad pairs.
We will use dynamic programming to do this. $$$dp(i)(j)$$$ stores the number of permutations of length $$$i$$$ with $$$j$$$ bad pairs. Now, if we want to place the element $$$i + 1$$$ to make permutations of length $$$i + 1$$$. We can place it as the last element of any $$$j + 1$$$ increasing subarrays to retain the number of bad pairs or place it anywhere else to increase the number of bad pairs by $$$1$$$.
So, for some $$$n$$$ and $$$k$$$. The answer becomes the sum of all $$$dp(n)(j)$$$ values for $$$0 \leq j \leq k - 1$$$.
Time complexity : $$$O(nk)$$$
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;
#define int long long
#define ll long long
#define ull unsigned long long
#define vi vector<int>
#define vll vector<long long>
#define pr pair<int, int>
#define vpi vector<pair<int,int>>
#define vpll vector<pair<ll,ll>>
#define mapi map<int,int>
#define mapll map<ll,ll>
#define all(x) x.begin(), x.end()
#define yes cout<<"YES\n"
#define no cout<<"NO\n"
const int M = 1e9 + 7;
int dp[5001][5001];
void solve(){
int N = 5000;
dp[1][0] = 1;
for(int i = 2; i <= N; i++){
for(int j = 0; j < i; j++){
dp[i][j] = (dp[i][j] + dp[i - 1][j] * (j + 1)) % M;
dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] * (i - j)) % M;
}
}
for(int i = 1; i <= N; i++){
for(int j = 1; j < i; j++){
dp[i][j] = (dp[i][j] + dp[i][j - 1]) % M;
}
}
int t; cin>>t;
for(int i = 0; i < t; i++){
int n, k;
cin>>n>>k;
cout<<dp[n][k - 1]<<endl;
}
}
signed main(){
std::ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
while(t--){
solve();
}
return 0;
}
Author : synthborne
What about primes $$$p$$$ such that $$$ p \leq n / 3$$$.
What about primes $$$p$$$ such that $$$n / 3 < p \leq n / 2$$$.
If we can take a prime $$$p$$$ into the answer, there always exists a way to take all the multiple of $$$p$$$ into the answer. So, we should talk about what all primes we can take. From a first glance, it might seem that we can take all primes $$$p$$$ such that $$$p \leq n / 2$$$, but this is not the case.
If we want to place a prime $$$p$$$ in our construction which is not at the either ends of the array. We have to place non coprime values to its left and right, and the best we can do is to place the numbers $$$2p$$$ and $$$3p$$$ to its left and right. So, we can place any prime $$$p$$$ such that $$$p \leq n / 3$$$. A seemingly nice construction may look something like this : $$$2p_1$$$, $$$p_1$$$, $$$3p_1$$$, $$$3p_2$$$, $$$p_2$$$, $$$2p_2$$$, $$$2p_3$$$, $$$p_3$$$,...
If we are placing primes at the either ends of the array (only $$$2$$$ such positions exist). We will only need $$$1$$$ non coprime number beside it (there is no other side) and best we can do is to place $$$2p$$$. The construction looks something like this :
Left end : $$$p_1$$$, $$$2p_1$$$,...
Right end : ..., $$$2p_2$$$, $$$p_2$$$
So, we can take all primes $$$p$$$ such that $$$p \leq n / 3$$$ and at most $$$2$$$ primes such that $$$n / 3 < p \leq n / 2$$$. It is also always possible to take all the multiples of the taken primes into the construction, an exercise left to the readers.
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;
#define int long long
#define ll long long
#define ull unsigned long long
#define vi vector<int>
#define vll vector<long long>
#define pr pair<int, int>
#define vpi vector<pair<int,int>>
#define vpll vector<pair<ll,ll>>
#define mapi map<int,int>
#define mapll map<ll,ll>
#define all(x) x.begin(), x.end()
#define yes cout<<"YES\n"
#define no cout<<"NO\n"
const int N = 1e6;
int sieve[N + 1];
vector<int> primes;
void solve(){
int n;
cin>>n;
vector<int> v;
int idx = 0;
for(int i = 0; i < primes.size(); i++){
if(primes[i] <= n / 3){
v.push_back(primes[i]);
}
else{
idx = i;
break;
}
}
if(primes[idx] <= n / 2) v.push_back(primes[idx]);
if(primes[idx + 1] <= n / 2) v.push_back(primes[idx + 1]);
int hash[n + 1]{0};
for(auto &i : v){
for(int j = i; j <= n; j += i) {
hash[j] = 1;
}
}
ll sum = 0;
for(int i = 1; i <= n; i++){
if(hash[i]) sum++;
}
cout<<sum<<endl;
}
signed main(){
std::ios_base::sync_with_stdio(false);
cin.tie(nullptr);
for(int i = 2; i <= N; i++){
if(sieve[i]) continue;
for(int j = 2 * i; j <= N; j += i){
sieve[j] = 1;
}
}
for(int i = 2; i <= N; i++){
if(!sieve[i]) primes.push_back(i);
}
int t = 1;
cin>>t;
while(t--){
solve();
}
return 0;
}
Author : paramveer5404, synthborne
We will first compute the information, the sequence of ranges $$$(l, r)$$$ in the order of rain blocks falling on them (from $$$r$$$ to $$$l$$$, $$$h$$$ times) increasing the height of the range by $$$h$$$ (maximum $$$h$$$ possible until the range changes). The number of such ranges will obviously be of $$$O(n)$$$.
We can easily find this by maintaining a monotonic stack of three variables, left end of the range, right end of the range and current height of this range. We will push a range $$$(l, r, cur_h)$$$ into our stack if the stack's current topmost range's height $$$h$$$ is larger than height of current range $$$cur_h$$$. Otherwise, it would mean that rain blocks will fall on current stack's topmost range now until its height $$$h$$$ becomes larger than $$$cur_h$$$. We can simulate this process by popping the ranges from the stack and performing necessary calculations of range attributes. This takes $$$O(n)$$$.
This gives us the sequence of ranges in the order of time of rain blocks falling on them. We can also find the total time it takes to fill a range $$$(l, r, h)$$$ which is equal to $$$(r - l + 1) \times h$$$. This may also give us the timestamp when rain blocks fall on any range by finding the prefix sum of time taken on each range in this sequence.
Sort the queries with respect to time $$$t$$$. We will answer the queries in ascending order of $$$t$$$. For some $$$t$$$, we need to simulate updates for every range which have already been acted upon by the rain blocks (whose ending times are lesser than $$$t$$$) by using a segment tree. For some range $$$(l, r, h)$$$, we can add $$$h$$$ into the range $$$(l, r)$$$ to simulate this.
For some query $$$(pos, t)$$$ and the range with largest start time $$$\leq t$$$ be $$$(l, r)$$$. There are two possible cases :
- If $$$pos$$$ is not inside $$$(l, r)$$$. We can simply return the answer from the segment tree, the value at $$$pos$$$.
- Otherwise, if $$$pos$$$ is inside $$$(l, r)$$$. We also need to simulate the current falling process on this range. So, we need to add the increase in height from this process in addition to the segment tree value at $$$pos$$$.
Time complexity : $$$O(nlogn)$$$
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> ordered_set;
#define int long long
#define ll long long
#define ull unsigned long long
#define vi vector<int>
#define vll vector<long long>
#define pr pair<int, int>
#define vpi vector<pair<int,int>>
#define vpll vector<pair<ll,ll>>
#define mapi map<int,int>
#define mapll map<ll,ll>
#define all(x) x.begin(), x.end()
#define yes cout<<"YES\n"
#define no cout<<"NO\n"
const int N = 2e5 + 5;
int seg[4 * N];
void update(int v, int l, int r, int L, int R, int val){
if(L > R)
return;
if(L == l && R == r){
seg[v] += val;
return;
}
int mid = (l + r) / 2;
update(2 * v + 1, l, mid, L, min(R, mid), val);
update(2 * v + 2, mid + 1, r, max(mid + 1, L), R, val);
}
int query(int v, int l, int r, int pos) {
if(l == r){
return seg[v];
}
int mid = (l + r) / 2;
if(pos <= mid){
return seg[v] + query(2 * v + 1, l, mid, pos);
}
else{
return seg[v] + query(2 * v + 2, mid + 1, r, pos);
}
}
void solve(){
int n, q;
cin>>n>>q;
int arr[n + 1];
for(int i = 0; i < n; i++) cin>>arr[i];
arr[n] = -1;
int l = 0;
vector<pair<pr, int>> v;
for(int i = 1; i <= n; i++){
if(arr[i] != arr[i - 1]){
v.push_back({{l, i - 1}, arr[i - 1]});
l = i;
}
}
v.push_back({{n, n}, INT_MAX});
vector<pair<pr, int>> order;
stack<pair<pr, int>> st;
order.push_back({{0, 0}, 0});
//monotonic stack precomputation
for(int i = 0; i < v.size(); i++){
int l = v[i].first.first;
int r = v[i].first.second;
int h = v[i].second;
if(st.empty()){
st.push({{l, r}, h});
continue;
}
if(st.top().second < h){
int curl = st.top().first.first;
int curr = st.top().first.second;
int curh = st.top().second;
st.pop(); i--;
if(st.empty()){
if(i == v.size() - 2) break;
order.push_back({{curl, curr}, (curr - curl + 1) * abs(h - curh)});
st.push({{curl, curr}, h});
continue;
}
int prevl = st.top().first.first;
int prevr = st.top().first.second;
int prevh = st.top().second;
int val = min(abs(curh - prevh), abs(curh - h));
order.push_back({{curl, curr}, (curr - curl + 1) * val});
if(abs(curh - prevh) <= abs(curh - h)){
st.pop();
st.push({{prevl, curr}, prevh});
}
else{
st.push({{curl, curr}, h});
}
}
else if(st.top().second == h){
int curl = st.top().first.first;
st.pop();
st.push({{curl, r}, h});
}
else{
st.push({{l, r}, h});
}
}
// prefix array of range updates
for(int i = 1; i < order.size(); i++) {
order[i].second += order[i - 1].second;
}
for(int i = 0; i <= 4 * n; i++) seg[i] = 0;
int answer[q]; int p = 1;
vector<pair<pr, int>> queries;
for(int i = 0; i < q; i++){
int time, pos;
cin>>time>>pos; pos--;
queries.push_back({{time, pos}, i});
}
// offline queries
sort(all(queries));
for(int i = 0; i < q; i++){
int time = queries[i].first.first;
int pos = queries[i].first.second;
int idx = queries[i].second, l, r;
while(p < order.size() && order[p].second <= time){
l = order[p].first.first;
r = order[p].first.second;
int val = (order[p].second - order[p - 1].second) / (r - l + 1);
update(0, 0, n - 1, l, r, val);
p++;
}
if(p == order.size()){
int d = time - order[p - 1].second;
d = d - (n - pos) + n;
answer[idx] = arr[pos] + query(0, 0, n - 1, pos) + d / n;
continue;
}
int d = time - order[p - 1].second;
l = order[p].first.first;
r = order[p].first.second;
d = d - (r - pos + 1) + (r - l + 1);
if(l <= pos && pos <= r){
//in the range
answer[idx] = arr[pos] + query(0, 0, n - 1, pos) + d / (r - l + 1);
}
else{
// outside the range
answer[idx] = arr[pos] + query(0, 0, n - 1, pos);
}
}
for(int i = 0; i < q; i++) cout<<answer[i]<<" ";cout<<endl;
}
signed main(){
std::ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
while(t--){
solve();
}
return 0;
}
Author : synthborne
F : 580057F - Everblossom Tree
Turns out the participants had much easier solutions than the author solution.
Coming soon...