A. Dalton the Teacher
题意
有 n 个数 a1,a2,…,an。
现在每次操作可以交换两个数,请求出最少的操作数,使得 ∀1≤i≤n,ai=i。
解法
设当前满足 ai=i 的数数量为 x。
可以证明,答案为 ⌊2x+1⌋。
证明:
必要性:一次操作只能调换两个数的位置,至少需要 ⌊2x+1⌋ 次操作才能调换每一个数。
充分性:我们构造一个合法方案。如果 x 是偶数,我们直接两两一换即可。如果 x 是奇数,我们先将 x−1 两两一换,显然最后一个数一定能在前 x−1 个数中找到一个能和它换的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include<bits/stdc++.h> using namespace std;
using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__)
int read(){ int f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; }
const int N = 1e5+5; void solve(int kase){ int n=read(); int cnt =0; for(int i=1;i<=n;i++){ int x=read(); if(x==i)cnt++; } printf("%d\n",(cnt+1)/2); }
int main(){ int T=read(),TT=1; while(T--){ solve(TT++); } return 0; }
|
B. Longest Divisors Interval
题意
给定 n (n≤1018),求出最长区间 [l,r],使得 ∀l≤i≤r,n 是 i 的倍数(我们定义区间 [l,r] 的长度为 r−l+1)。
解法
所有的合法最长区间中,一定有一个是从 1 开始的。
证明:
不妨设 [l,r] 是答案的合法最长区间。
∵ 在 x 个连续整数中有一个 x 的倍数,
∴ [l,r] 中包含 1,2,…,r−l+1 的倍数。
∵ ∀l≤i≤r,n 是 i 的倍数,
∴ n 是 1,2,…,r−l+1 的倍数。
∴ [1,r−l+1] 是一个合法解,且长度与 [l,r] 相同。
因此只需要从 1 开始不断枚举 r 即可。因为 lcm(1,2,…,43)=9419588158802421600>1018,所以最多枚举 42 个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include<bits/stdc++.h> using namespace std;
using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__)
int read(){ int f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; }
void solve(int kase){ ll n = read<ll>();
ll x = 1; while(n%x==0)x++;
printf("%lld\n",x-1); }
int main(){ int T=read(),TT=1; while(T--){ solve(TT++); } return 0; }
|
C2. Dual (Hard Version)
题意
给定 n (n≤20) 个数 a1,a2,…,an (−20≤ai≤20)。现在可以进行一次操作,选定 i,j(不一定不同),使得 ai←ai+aj。
要使这个序列单调不减,请构造出一个最多使用 31 次的方案。
解法
注意到若这个序列全部非负,我们可以执行 ai+1←ai+1+ai 操作 n−1 次,使得整个序列单调不减。同理若这个序列全部非正,我们也可以执行 ai−1←ai−1+ai 操作 n−1 次,使得整个序列单调不减。
上面这个操作需要使用 19 步,因此我们考虑如何在 12 步内将这个序列全部变为同号。
现在序列中既有负数也有正数(如果没有,直接用上面的方法处理)。
不失一般性,我们令最小数的绝对值大于等于最大数的绝对值。考虑两种情况:
- 正数的个数小于等于 12 个,直接用最小数加给所有正数,在 12 步内将序列变为全负。
- 正数个数大于等于 13 个,负数的个数小于等于 7 个。我们将任意一个正数用五次操作翻五番(ai←ai+ai),这样这个数至少等于 32,然后我们用这个翻五番的数加给每一个负数,这样就可以在 5+7=12 步操作将所有数变为正数。
若最小数的绝对值小于最大数的绝对值,同理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| #include<bits/stdc++.h> using namespace std;
using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__)
int read(){ int f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; }
const int N = 25; int a[N]; void solve(int kase){ int n=read(); vector<pii> v; for(int i=1;i<=n;i++){ a[i]=read(); v.push_back({a[i],i}); } sort(all(v));
int cntneg=0,cntpos=0;
for(int i=0;i<n;i++){ if(v[i].first<0)cntneg++; if(v[i].first>0)cntpos++; }
int posmax = max_element(a+1,a+1+n)-a; int posmin = min_element(a+1,a+1+n)-a;
if(cntpos==0){ printf("%d\n",n-1); for(int i=n;i>1;i--){ printf("%d %d\n",i-1,i); } return; } if(cntneg==0){ printf("%d\n",n-1); for(int i=1;i<n;i++){ printf("%d %d\n",i+1,i); } return; }
vector<pii> ans; if(abs(a[posmin])>=abs(a[posmax])){ if(cntpos<=12){ for(int i=0;i<n;i++){ if(v[i].first>0){ ans.push_back({v[i].second,posmin}); } }
for(int i=n;i>1;i--){ ans.push_back({i-1,i}); } } else{ for(int i=0;i<5;i++){ ans.push_back({posmax,posmax}); } for(int i=0;i<n;i++){ if(v[i].first<0){ ans.push_back({v[i].second,posmax}); } } for(int i=1;i<n;i++){ ans.push_back({i+1,i}); } } } else{ if(cntneg<=12){ for(int i=0;i<n;i++){ if(v[i].first<0){ ans.push_back({v[i].second,posmax}); } }
for(int i=1;i<n;i++){ ans.push_back({i+1,i}); } } else{ for(int i=0;i<5;i++){ ans.push_back({posmin,posmin}); } for(int i=0;i<n;i++){ if(v[i].first>0){ ans.push_back({v[i].second,posmin}); } } for(int i=n;i>1;i--){ ans.push_back({i-1,i}); } } }
cout<<ans.size()<<'\n'; for(auto x:ans){ cout<<x.first<<' '<<x.second<<'\n'; a[x.first]+=a[x.second]; }
assert(is_sorted(a+1,a+1+n)); }
int main(){ int T=read(),TT=1; while(T--){ solve(TT++); } return 0; }
|
D. Earn or Unlock
题意
有 n (n≤105) 张牌,权值为 v1∼vn。现在只有第一张牌解锁了,后面的牌全部是锁住的。
现在你可以进行操作:选定一张解锁了的牌 i:
- 再解锁后面 vi 张牌;
- 答案加上 vi。
求答案最大值。
解法
注意到一个事实:若总共恰好解锁了 x 张牌,则答案为 v1+v2+…+vmax(n,x)−(x−1)。
设 dpi,j 代表能否使用前 i 张牌恰好解锁 j 张牌。
初始条件:dp0,1=1。
转移:
dpi,j=max(dpi−1,j,dpi−1,j−vi)
事实上,第二个转移 dpi,j=dpi−1,j−vi 当且仅当 j−vi≥i 时才能成功转移,为此我们在每次操作后,都将 dpi,i 置 0,保证都是从合法的状态转移的。
对于 j 只需要算到 2n 就够了,显然解锁超过 2n 张牌是更劣的。
用 bitset
优化,复杂度为 O(wn2),勉强够。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #include<bits/stdc++.h> using namespace std;
using ll=long long; using ull=unsigned long long; using pii=pair<int,int>; #define all(x) x.begin(),x.end() #define mem0(x) memset(x,0,sizeof(x)) #define YES puts("YES") #define NO puts("NO") #define Yes puts("Yes") #define No puts("No") #define errorf(...) fprintf(stderr, __VA_ARGS__)
int read(){ int f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; }
const int N = 2e5+5; bitset<N> dp; int a[N]; ll s[N];
int main(){ int n=read(); for(int i=1;i<=n;i++){ a[i]=read(); s[i]=s[i-1]+a[i]; }
dp[1]=1; ll ans = 0; for(int i=1;i<=n;i++){ dp=dp|dp<<a[i]; if(dp[i]){ ans = max(ans, s[i]-i+1); } dp[i]=0; } for(int i=n+1;i<=n*2;i++){ if(dp[i]){ ans = max(ans,s[n]-i+1); break; } }
printf("%lld\n",ans); return 0; }
|