Color the Picture( GOOD)
题意: 给定二维网格矩阵。可以使用
k
k
k种颜色给每个网格涂色,其中第i种颜色至多可涂
a
i
a_i
ai个网格。问是否存在一种方法使得每个格点上下左右四个格点至少有三个格点与其自身颜色完全一致。(这里的上下左右假设是环状的上下左右即假设矩阵上下相连左右相连)
思路:由于有对称性所以随机选一格点涂上任意一种颜色,然后随机选其邻居三个格点涂上与其自身同样的颜色。然后不断试探可知要想达成目标必须每种颜色能图满涂上2列或2行。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
bool solve(int a[],int m,int n,int k)
{
int pre = 0;
for(int i = k - 1;i>=0;i--)
{
if(a[i]/n < 2) return false;
pre += a[i]/n - 2;
if(2 * (k - i) <= m && (2 * (k - i) + pre)>=m) return true;
}
return false;
}
int main()
{
int t;
scanf("%d",&t);
for(int i = 0;i<t;i++)
{
int n,m,k; scanf("%d%d%d",&n,&m,&k);
int a[k];
for(int j=0;j<k;j++)
{
scanf("%d",&a[j]);
}
sort(a,a+k);
if(solve(a,n,m,k) || solve(a,m,n,k)) printf("YES\n"); else printf("NO\n");
}
system("pause");
return 0;
}
Doremy’s IQ(1600 GOOD)
题意:给定数组a和一整数q。从前往后遍历数组a,设当前遍历到a[i],可以进行下列操作之一:
- count++。如果a[i]>q则q- -否则什么都不做.
- count不变
求使得count最大时对应count++的i集合。
数据范围:
l
e
n
(
a
)
≤
1
e
5
,
1
e
9
≥
a
[
i
]
≥
1
len(a) \leq 1e^5,1e^9 \geq a[i]\geq1
len(a)≤1e5,1e9≥a[i]≥1
题解:
如何分析最优解所满足的性质减小搜索空间呢?
找一个解假定他是最优解并看他第一个使得q减少的点(假定为点
a
q
a_q
aq),在此点之后若有跳过的点我们看第一个跳过的点(假设为点
a
p
a_p
ap),实际上我们跳过
a
q
a_q
aq转而去不跳过
a
p
a_p
ap得到的解肯定必原来假定的最优解更优。重复执行上述步骤容易看出最优解必须满足的性质是有某个点i,i到n每个下标均要count++,i之前下标是否count++仅是
a
j
≤
q
a_j \leq q
aj≤q对应的下标j.由于i-n全部都要count++显然i越小则可能跳过的点越少即可能参与的比赛越多。我们二分查找这样的最小的i即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int check(int q,int m,int n,int a[])
{
int count = 0;
for(int i = 0 ;i < m;i++) if(a[i]<q) count++;
//q的智商是否能够维持住接下来的所有
count += (n - m);
for(int i = m;i<n;i++)
{
if(a[i] <= q) continue;
else if(a[i] > q)
{
q--;
if(q < 0) return -1;
}
}
return count;
}
int main()
{
int t;scanf("%d",&t);
for(int i = 0;i<t;i++)
{
int n,q; scanf("%d%d",&n,&q);
int a[n];
for(int k = 0;k<n;k++) scanf("%d",&a[k]);
int l = 0,r = n;
int ans = 0;
while(l < r)
{
int m = (l + r) >> 1;
int temp = check(q,m,n,a);
if(temp != -1) r=m; else l = m + 1;
}
for(int k = 0;k<l;k++) if(a[k]<=q) printf("1"); else printf("0");
for(int k = l;k<n;k++) printf("1");
printf("\n");
}
return 0;
}
Toss a Coin to Your Graph…(1900)
题意:给定有向图G,每个结点上有个数字,求所有(含k个结点的通道中结点数字最大值)的最小值。
其中
1
≤
k
≤
1
e
18
,
图
结
点
数
量
≤
2
e
5
,
图
边
数
量
≤
2
e
5
1 \leq k \leq 1e^{18},图结点数量 \leq 2e^5,图边数量\leq 2e^5
1≤k≤1e18,图结点数量≤2e5,图边数量≤2e5
解答:将结点按数字从小到大排序,按序新加入一个结点并考察当前结点集合导出的子图则当且仅当图中至少存在一个环或存在一条长至少为k-1的简单路径时该子图就满足条件。做的时候感觉边数量较少总想着增量更新找到第一个满足条件的点,这个点的引入使得子图中存在了长至少为k-1的路径或一个环,但始终行不通。我们换种思路,采用二分查找这个突变点,突变点对应的数字就是答案。每次找最长路径+判环为一遍
O
(
m
+
n
)
O(m+n)
O(m+n)的dfs,二分为logn复杂度,总复杂度为nlogn级别。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int dfs(int dp[],bool vis[],bool instack[],int cur,int &maxans,int m,vector<int> adj[])
{
instack[cur] = true;
if(vis[cur]==false)
{
for(int v:adj[cur])
{
if(v <= m)
{
if(instack[v]) return -1;
int p = dfs(dp,vis,instack,v,maxans,m,adj);
if(p == -1) return -1; //发现环
dp[cur] = max(dp[cur],p+1);
}
}
}
vis[cur]=true;
instack[cur] = false;
maxans = max(dp[cur],maxans);
return dp[cur];
}
bool judge(ll k,vector<int> adj[],int n,int m)
{
bool instack[n]; memset(instack,0,n*sizeof(bool));
bool vis[n]; memset(vis,0,n*sizeof(bool));
int dp[n]; memset(dp,0,n*sizeof(int));
int max_ans = 0;
for(int i = 0;i<=m;i++)
{
if(!vis[i])
{
if(dfs(dp,vis,instack,i,max_ans,m,adj) == -1) return true; //找到环
}
}
if(max_ans >= k - 1) return true;
return false;
}
int main()
{
ll n,m,k;
cin>>n>>m>>k;
pair<ll,ll> a[n];
for(int i = 0;i<n;i++){int a_;scanf("%d",&a_);a[i].first=a_;a[i].second=i;}
sort(a,a+n);
int revm[n];
for(int i = 0;i<n;i++) {revm[a[i].second] = i;}
vector<int> adj[n];
for(int i = 0;i<m;i++){int u,v;scanf("%d%d",&u,&v);u--;v--;adj[revm[u]].push_back(revm[v]);}
int l = 0,r = n;
while(l < r)
{
int m = (l + r) >> 1;
//判断[0,m]是否满足
if(!judge(k,adj,n,m)) l = m + 1; else r = m;
}
//r--;
if(r>=0&&r<=n-1)printf("%d\n",a[r].first); else printf("-1\n");
system("pause");
return 0;
}
Good Key, Bad Key(1600)
题意: 给定长度为n的数组a。设count=0。从前往后遍历数组a,每遇到一个元素可做如下操作之一(设当前遍历到a[i]):
- count减k然后count+=a[i]
- a[i],a[i+1]…a[n-1]都除以2(向下取整)然后count+=a[i]
题解:最近这种贪心题也不能很快抓住把柄故记录一下。显然不能有任何的后者操作处于前者之前,否则将其调换顺序得到更优的解。于是我们暴力枚举每个前缀做第一种操作,后面所有都做第二种操作。再注意数据范围即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int main()
{
int t; scanf("%d",&t);
for(int i =0;i<t;i++)
{
int n;
scanf("%d",&n);
long long k;
scanf("%lld",&k);
ll a[n]; ll sum = 0;
for(int o = 0;o<n;o++) {scanf("%lld",&a[o]);sum+=a[o];}
ll ans = sum - k * n; ll pre = 0;
for(int j = n - 1;j>=0;j--) //a[j,n-1]的全部都除以2计算
{
pre+=a[j];
ll temp = sum - pre - k*(ll)j;
for(int o = j;o<min(n,j+32);o++)
{
int count = o - j + 1;
ll ao = a[o];
while(count--) ao=ao>>1;
temp+=ao;
}
ans = max(ans,temp);
}
cout << ans << endl;
}
system("pause");
return 0;
}
Mark and Lightbulbs(1800)
题意:给定0-1字符串s。每一次操作可以选择一个整数i满足 s [ i − 1 ] ≠ s [ i + 1 ] s[i-1]\neq s[i+1] s[i−1]=s[i+1],然后将 s [ i ] s[i] s[i]这一位翻转。现有一目标0-1字符串t。求将s转换到t的最少操作次数。如果不能完成返回-1.
解法1:
注意连续的1段或0段只能在边缘操作且这些段只可能是其中1或0数量的减少而不会消失
观察将s变化的过程,如果不考虑边界情况注意到s中连续的1组成的段题目中的操作实际上是在这些段的首尾添加或删除1并且两个被0分离开的1段不能合并在一块(由于101中间的0不能转换为1)。如果考虑边界限制s[0]需要和t[0]相等,s[n-1]需要和t[n-1]相等才有可能达成目标。每一次扩展1段或收缩1段恰好都需要1次操作。显然存在一种合理的拓扑顺序使得可以贪心地只收缩或扩张每个对应0-1段边界位置的差值从而达成目标。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int main()
{
int q; cin>>q;
for(int i= 0;i<q;i++)
{
int n; cin>>n;
string s; string t;
cin >> s; cin >> t;
if(s[0]!=t[0] || s[s.size() - 1] != t[t.size() - 1]) {printf("-1\n");continue;}
vector<pair<int,int>> a;vector<pair<int,int>> b;
int k = 0;
while(k < s.size())
{
while(k<s.size()&&s[k]=='0')k++;
if(k<s.size())
{
int st = k;
while(k<s.size()&&s[k]=='1')k++;
a.push_back({st,k-1});
}
}
k = 0;
while(k<t.size())
{
while(k<t.size() && t[k]=='0')k++;
if(k<t.size())
{
int st = k;
while(k<t.size() && t[k]=='1') k++;
b.push_back({st,k-1});
}
}
if(a.size()!=b.size()) {printf("-1\n");}
else
{
ll ans = 0;
for(int o = 0;o<a.size();o++)
ans += abs((ll)a[o].first - (ll)b[o].first) + abs((ll)a[o].second - (ll)b[o].second);
printf("%lld\n",ans);
}
}
system("pause");
return 0;
}
解法2(异或差分):注意到讨论范围仅涉及0-1序列。考察异或差分序列
b
0
=
s
[
0
]
⊕
s
[
1
]
,
⋯
b
[
i
]
=
s
[
i
]
⊕
s
[
i
+
1
]
b_0 = s[0] \oplus s[1],\cdots b[i]=s[i]\oplus s[i+1]
b0=s[0]⊕s[1],⋯b[i]=s[i]⊕s[i+1].则每次操作相当于将b数组不相邻的两个位置进行交换。如果能达成目标则这样的交换一定和t字符串生成的异或差分序列相同。另一方面,使得s[0]=t[0]是必要条件因为它们无法被修改。
ps:这道题和之前某道差分题很像,即将问题转换为差分数组讨论更明确。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
int main()
{
int q; cin>>q;
for(int i= 0;i<q;i++)
{
int n; cin>>n;
string s; string t;
cin >> s; cin >> t;
if(s[0]!=t[0]) {printf("-1\n");continue;}
int xordifa[n-1],xordifb[n-1];
for(int j = 0;j<n-1;j++){xordifa[j]=(s[j]-'0')^(s[j+1]-'0');xordifb[j]=(t[j]-'0')^(t[j+1]-'0');}
int p = 0,q = 0;
ll ans = 0;
int l = 0,r=0;
while(p<n-1&&q<n-1)
{
while(p<n-1&&xordifa[p]==0)p++;
while(q<n-1&&xordifb[q]==0)q++;
if(p<n-1&&q<n-1) {ans+=abs((ll)p-(ll)q);}
else break;
p++;q++;
}
bool f = true;
while(p<n-1) {if(xordifa[p++]==1) {f=false;break;}}
while(q<n-1) {if(xordifb[q++]==1) {f=false;break;}}
if(f) cout<<ans<<endl; else cout<<-1<<endl;
}
system("pause");
return 0;
}
总结:这种将一个东西转化到另一个东西关键在于寻找两者间的某种可能匹配。(可能是先匹配一部分再考虑子问题)