Prompt by me:

“Before purchasing a dragon, make sure to check the safety ratings from the National Dragon Transport Safety Administration.”

Thank you for purchasing the 2019 Volksdrachen Puk®! The stylish, compact, and efficient Puk sets a new standard in serpentine design.

Your dragon features:

- All-wing drive
- Tail-mounted backup camera
- Pitot tube airspeed indicator
- 11.5 inch bio-electrical touchscreen with Bluetooth support
- Sail-fin antenna
- Air lane-keeping assist
- Parking claw suitable for most surfaces
- Ten airbags with bronchial inflation
- Adaptive cruising altitude control
- Power steering

If you purchased the SX trim level or higher, you also have:

- Authentic scale upholstery
- Qi-compatible smartphone charging (with genuine qi energy)
- Integrated navigation with GPS sensor fusion
- Rear spoiler wings
- 3 USB ports
- Sporty racing stripes

Make sure to adjust mirrors and horns before riding.

Wear seatbelts at all times.

Texting while riding is dangerous and is illegal in the state of California. Keep your eyes on the sky at all times.

Fangs and claws are sharp. Volksdrachen Group are not responsible for injuries incurred through improper handling of clawtips.

The current fuel level is indicated by the dial on the right-hand side of the dashboard display. Fill the fire breath tank only with HD5 grade propane.

The Puk has three modes of operation: Standard, Sport, and Eco. Use the dial adjacent to the left wing to adjust the mode while riding. Eco mode maximizes gliding efficiency, while Sport mode gives you that extra boost of power when you want to feel the wind in your face.

The spare wing is located underneath the rear storage compartment. Do not fly more than 200 miles or faster than 100 miles per hour while using the spare wing. (Spare wing not included in all models.)

In the event of a bronchial sac blowout, the most important thing to remember is to stay calm. DO NOT PULL BACK OR RELEASE THE REINS—this is likely to result in an out-of-control tailspin. Instead, try to maintain stable flight while slowly dropping altitude.

Once you have landed safely, do not attempt to repair the sac yourself. Walk your dragon to the nearest Volksdrachen-certified repair centre or call a carrier.

]]>Prompt by /u/ElectronicLoad:

You’ve developed telekinesis, but to your dismay you can barely manage to lift a pound, with no hope of becoming any stronger

The potato wobbled. I concentrated, contorting my face. *Lift. Lift.*

Nothing. It was too heavy.

Zapped by lightning and dipped in radioactive sludge at the same time, and this is all I got. The ability to make vegetables wiggle.

Could I lift my own hand? No. I couldn’t even *feel* my own hand with my power. Apparently it didn’t work on living matter. No ripping the heart out of someone’s chest.

Frustrated, I walked to the other side of the kitchen and grabbed the chef’s knife out of the knife block. Overkill, but I really needed to smash something right about now. I laid the potato on a cutting board and cleaved it in two with a satisfying *thunk*. The two halves rolled slightly to either side of the knife.

Maybe I could lift two small objects more easily than one big object? With some effort, I levitated one half of the potato so that it floated in front of my face. I tried the other half— strained—

Both halves fell back to the cutting board. No. There was an absolute limit on how much force I could put out at once.

What a useless, shitty power.

I slammed the knife onto the countertop. This was going nowhere. I grabbed my coat off the rack and stormed out the front door of the house.

So many great powers in the world. Apsis could fly at the speed of a jet airliner, Pace could open a portal to the other side of the Earth, and I could tickle tubers without touching them. What I would do to wring a superhero’s oversized, muscular neck right now.

I was mad. More than that—I was *pissed*. I kicked the dirt in the yard with the toe of my shoe, and a cloud of dust flew into the air.

I *felt* the dust swirling.

Every individual grain of sand, every clay particle—I could sense its movement. I reached out with my mind, and stopped the motion of the dust cloud. It hovered in place as though time had frozen.

When I stepped into the cloud, the dust tingled as it pressed ever so slightly against my skin. I felt the particles move out of the way as my body passed through.

Apparently, that kick had dug up less than one potato’s worth of dirt.

I rushed back inside the house. Dirt still clung to my shoes as I ran across the tile. I pulled one shoe off, and nearly tripped and smacked my face into the countertop in excitement.

*Calm down*, I told myself. It was hard to calm down. I knew this would work.

I cleared away the knife and potato halves, then tapped the sole of the shoe against the cutting board. Soil, sand, and bits of organic material landed on the inch-thick wood slab. Gross, but that was something to worry about later.

I held up the cutting board, placing it between my face and a window. The board blocked my view of the early afternoon sunlight streaming through the glass. Very carefully, I levitated a single grain of the loose sand in front of the board. Then, I *pushed* it as hard as I could.

A tiny prick of light appeared in the center of the board.

Less than one pound of force, but concentrated on an area the size of a pinpoint. A nigh-invisible needle bullet.

Undetectable. *Unstoppable.*

“Mister Greene. Good to finally meet you.”

Greene extended his hand, and I shook it. He might have been smiling—I couldn’t tell, given his face-concealing green mask.

“The conference room is on the third floor”, Greene said. He gestured to the elevator.

I fiddled with my own mask on the ride up. A dollar store plastic injection-molded piece, on which I had glued some shaped strips of sandpaper. I didn’t have a costume otherwise, just a business suit with a jacket. An ugly rush job—I’d have to get something better later. If everything went well in this meeting, that wouldn’t be a problem.

Greene typed in a code on the electronic lock and opened the door to the conference room. He stepped inside, while I stayed behind in the doorway, my hand inside my suit jacket pocket.

Inside the room was a curved wooden conference table, with a projector and some miscellaneous computer equipment. Around the table were over a dozen supervillains sitting in office chairs. Most of them were C-listers at best, but there were a few masks that I recognized—Brittany Bloodletter, the Old Cicada, maybe one or two others.

“I would like to introduce our newest candidate member”, Greene said.

As I entered the conference room, I removed my hand from my pocket and released the handful of white quartz grains from my closed fist. They flew up in a ribbon, swirling across my mask and around the back of my head. Half of them, I formed into a razor-thin mandala of geometric shapes enclosing my face. The rest, I molded into scintillating bracelets around my forearms.

“This”—Greene gestured to me—“is *Pocket Sand*.”

*[Yes, I wrote all that just to cash in on a stale meme. And yes, I know that raw potatoes are still alive.]*

Prompt by /u/BronzeHeart92:

Your roommate is a supervillain with the ambitions to take over the city. Nothing out of ordinary, right? Thankfully, he prefers to spend the time watching b-movies and munching on popcorn.

“So, Mike, what’s on for tonight?”

Hearing my name caught my attention just enough for me to realize I had heard my name. I was sitting on the couch, and the sound came from behind me. “Did you say something?”, I asked, craning my head around.

Alex hauled his old laptop out of its case. The thing weighed like ten pounds without any cables or peripherals—I knew from past experience, from that time I had to lug it through the airport. The laptop clunked as he set it down on the fake wood surface of the kitchen table.

“Yeah”, Alex said. “I said, ‘what’s on for tonight?’.” He repeated the words slowly, enunciating each sound like he was talking over a bad phone connection. I hated when people did that. I wasn’t listening the first time; I *was* listening now. He didn’t have to act like he was talking to a toddler with a learning disability. *Condescending.*

I decided to answer the question anyway. “*Maniac Cop 2* is on, that’s what. In”—I checked the clock—”twelve minutes. I’m flipping channels until then.”

“I didn’t know there was a *Maniac Cop 1*“, Alex said.

“I didn’t either”, I replied, turning back to the screen, “but somehow I doubt continuity is of paramount importance in this series.”

Alex smirked. “Well, it’s not like it matters anyway. You won’t be able to see what’s going on on that shitpile of a TV.”

I frowned. “Really, again? Knock it off already with the TV. It works fine, it’s been working fine.”

“They haven’t even made new CRTs for like a decade. They’ve got, like, organic plasma now or some shit.”

“You got some serious pot-kettle problems going on there”, I said, tapping the channel button on the remote idly. “When are you going to break down and just buy a Chromebook so that you can use your gym membership instead of getting all your exercise lifting your computer?”

“I’ll tell you when”, Alex said, deadly serious. “When *every single* Newgrounds game has been ported to work without Flash, that’s the day I’ll *consider* it.”

And he said I was living in the past.

I resumed “watching” the images flicker across the phosphorescent screen. The set was big and boxy, almost as deep as it was wide, with a huge bezel around the edges. It really was a lousy picture, too.

Of course, I would never give up the old TV. I would terminate Alex sooner, if necessary, although I hoped it wouldn’t come to that—cleaning up a body is an awful inconvenience.

*Two more years*, I reminded myself. Two more years of absorbing energy through my eyes from the electron gun. Two more years until my power was fully charged, and my plan could finally come to fruition.

Two more years.

]]>From Joseph Converse, a puzzle of digital manipulation:

Imagine taking a number and moving its last digit to the front. For example, 1,234 would become 4,123. What is the smallest positive integer such that when you do this, the result is exactly

doublethe original number? (For bonus points, solve this one without a computer.)

Here is the solution… **(spoiler warning!)**

Let \(N\) be the “original” number we are searching for. Write \(N = 10 m + n\), where \(n \in [1..9]\) is the last digit and \(m\) is the rest of the digits. We have \(m \in [10^{k – 1}..10^k – 1]\) for some \(k \in \mathbb{N}^+\). We want to find values satisfying

\(2 \cdot (10 m + n) = 10^k n + m\)We can rewrite this to solve for \(m\) in terms of \(k\) and \(n\):

\(20 m + 2 n = 10^k n + m\)

\(19 m = (10^k – 2) n \qquad (1)\)

Since 19 is prime and \(n \lt 19\), we can apply Euclid’s lemma to conclude that 19 divides \((10^k – 2)\). This is the same as

\(10^k \equiv 2 \quad\pmod{19}\)This is a discrete logarithm problem with solution

\(k = 17 + 18 i\) for all \(i \in \mathbb{N} \qquad (2)\)

(This implies that any solution \(N\) must have at least 17 digits. Good thing we didn’t try a brute force search for \(N\)!)

At this point we just need to pick \(i\) and \(n\), and the values for \(k\) and \(m\) will follow immediately from (1) and (2). We have to choose \(i\) and \(n\) so that the constraint \(m \in [10^{k – 1}..10^k – 1]\) is satisfied. Certainly, we will get the smallest solutions from \(i = 0\) if such solutions exist, since increasing \(i\) increases \(N\) much faster than increasing \(n\). This gives us \(k = 17\). Substituting, we have:

\(10^{16} \leq \frac{10^{17} – 2}{19} n \leq 10^{17} – 1\)The smallest \(n\) satisfying this is \(n = 2\). Putting it all together, we have

\(i = 0,\ k = 17,\ n = 2\)

\(m = \frac{10^k – 2}{19} n = 10526315789473684\)

\(N = 10 m + n = 105263157894736842\)

And indeed, \(2 N = 210526315789473684\).

]]>

Recall again the definition of the 0-1 integer programming problem:

Input: \(m\)-by-\(n\) integer matrix \(A\) and length-\(m\) integer vector \(b\)

Output: true if and only if there exists a length-\(n\) 0-1 vector \(x\) such that \(Ax = b\)

A solution vector \(x\) needs to meet two criteria: it must be contain only zeros and ones, and it must satisfy \(Ax = b\). It is easy to generate a vector that meets either of these two criteria on their own; it is only the combination of the two that makes the problem hard.

For the second criterion, generating a vector that satisfies \(Ax = b\) can be done by reducing the augmented matrix \((A|b)\) to reduced row echelon form (RREF). This gives us another system of linear equations that has the same solution set as the original, but with the coefficients in a structured format. Once the matrix is in RREF, it is easy to determine whether the system of linear equations has a solution, and if it does to substitute the remaining free parameters with arbitrary values and read off a solution vector.

If a system of linear equations over \(n\) variables has any solutions at all, its solution space is an affine subspace of the \(n\)-dimensional Euclidean space. It can be a point, a line, a plane, or in general an \(m\)-hyperplane with \(m \leq n\). This suggests the following reformulation of the 0-1 integer linear programming problem, which might be called **0-1 affine subspace intersection**:

Input: description of an affine subspace \(S\) of \(n\)-dimensional Euclidean space

Output: true if and only if \(S\) contains any corner of the unit \(n\)-hypercube

This geometric rephrasing of 0-1 integer programming emphasizes how easy this hard problem might seem at first glance. The difficulty comes from the \(n\)-hypercube having a number of corners exponential in \(n\), which means we can’t get an efficient algorithm by just individually testing every corner for membership in the subspace. Any polynomial time algorithm for this problem would have to exploit some nontrivial geometric property of the problem.

Traditionally, converting a matrix to RREF is done by Gaussian elimination. What is the time complexity of Gaussian elimination? Most sources give it as \(O(n^3)\) for a matrix with maximum side length \(n\). However, this is actually misleading: \(O(n^3)\) is the *arithmetic complexity* of Gaussian elimination. Gaussian elimination is an example of an algorithm that is weakly polynomial time, but not strongly polynomial time, meaning that it runs in polynomial time in the real RAM model but does not in general run in polynomial time when implemented on a Turing machine. The worst-case *bit complexity* of standard Gaussian elimination is actually exponential!

Fortunately, there are known algorithms with worst-case polynomial bit complexity for converting matrices of rational numbers to RREF. Even better for our purposes, there are known algorithms with worst-case polynomial bit complexity for computing a description of the set of all *integer* solutions to a system of linear equations, as discussed in a fascinating 2015 article on the Gödel’s Lost Letter blog.

As described in this 2009 article referenced from the GLL blog post, the solution space of an integer matrix equation \(AX = B\) looks like this, where all of the variables are integer matrices and \(Z\) is a matrix of free parameter integer variables:

\(X = Q \begin{bmatrix} \overline{D}\,^{-1}\,\overline{PB} \\ Z \end{bmatrix}\)This implies that the set of integer solutions to a system of linear equations is an \(m\)-dimensional lattice of integer points embedded in \(n\)-dimensional Euclidean space. This lattice can be written as the span of a basis of linearly-independent integer vectors, plus an integer affine offset from the origin.

This suggests another representation of the 0-1 integer programming problem, which might be called **0-1 lattice intersection**:

Input: set of length-\(n\) integer vectors \(U\) and length-\(n\) integer vector \(v\)

Output: true if and only if there exists a length-\(n\) 0-1 vector \(x\) such that \(x – v\) is a member of the integer lattice spanned by \(U\)

This is reminiscent of the class of lattice problems in cryptography, some of which are likewise known to be NP-hard.

]]>What computational complexity class class does this problem fall into? Can we do it in polynomial time if we take the degree \(d\) to be a fixed constant?

As it turns out, this problem is NP-hard, even for \(d = 2\). The proof is straightforward and requires no algebraic geometry or other “hard” math—you just have to know the trick.

We can reduce from the 0-1 integer linear programming problem, which is one of Karp’s original 21 NP-complete problems. 0-1 integer programming is defined as follows:

Input: \(m\)-by-\(n\) integer matrix \(A\) and length-\(m\) integer vector \(b\)

Output: true if and only if there exists a length-\(n\) 0-1 vector \(x\) such that \(Ax = b\)

We can write the matrix equation \(Ax = b\) as a set of \(m\) linear equations in \(n\) variables. This gives us a system of polynomial equations, but without further constraints we are not guaranteed to end up with a solution that is valid for the original 0-1 integer programming problem, since the variables are allowed to range over \(\mathbb{C}\) rather than being restricted to 0 or 1.

However, this is easy to fix—we just need to add additional polynomial equations that constrain each variable to be either 0 or 1. It is sufficient to add an equation \({x_i}^2 – x_i = 0\) for each variable \(x_i\), since the solution set of this equation is exactly \(x_i \in \{0, 1\}\). This gives us a system of \(m + n\) polynomial equations in \(n\) variables with maximum degree 2, where an assignment of the variables is a solution of the system if and only if it is a solution to the original integer programming problem. Since this reduction is clearly possible to perform in polynomial time, we can conclude that determining whether a system of degree-2 polynomials with integer coefficients has a solution is NP-hard.

]]>There are obvious downsides to this approach, such as the potential for false positives (good comments that are incorrectly classified as spam, perhaps due to the infamous Scunthorpe problem) as well as the high rate of false negatives (spam comments that are not recognized as such and have to be deleted manually). However, word blacklists are available as a built-in feature of WordPress, so I don’t have to use a paid subscription blog spam filtering service such as Akismet. Also, the simplicity and controllability of the approach are nice.

In the rest of this post, I will list and describe all of the string filters I use, so that other bloggers can copy them if so desired.

The single most effective set of blacklisted strings that I use is a short list of common Cyrillic characters. Since this is an English language blog but a great deal of spam is written in Russian (or pseudo-Russian gibberish), this filter is very powerful for its small size. The particular list of characters that I use is taken from an article elsewhere on the Internet which, sadly, I can no longer find. The list is as follows:

д и ж Ч Б Џ Ђ ћ Р° Ѓ

Another common language that I receive spam comments in is Japanese. Almost all Japanese text can be efficiently filtered out with this even shorter list of characters:

。 ー の

Next, we have the medications. This is a very effective filter, but unfortunately the list has to be updated frequently as the distribution of drugs being pushed in the spam I receive changes over time. Also, *cialis* cannot be included, since as Wikipedia notes, it is contained as a substring in the common word *specialist*; nor *ambien*, as it is a substring of *ambient*. The brand name *ultram* is probably safe, however, unless I start posting Warhammer 40K content.

adderall alprazolam clomid clonazepam clopidogrel diazepam doxycycline effexor ephedrine ivermectin klonopin lasix lunesta oxymorphone phentermine restoril retin a retin-a sildenafil tetracycline tramadol ultram valacyclovir valium viagra vicodin xanax zoloft zolpidem

Next up, we have distinctive phrases that occur in certain fixed spam messages that get posted over and over again. This filter is not very effective in the long run, since the particular spam messages tend to change over time, but as a short-term fix to get rid of individual really persistent spammers, it can work pretty well.

going to put you in the freezer as punishment hard to find your site in google hard to find your website in google I noticed that your On-Page SEO is is missing a few factors I noticed your site lost rank in google missing out on at least 300 visitors per day We have decided to open our POWERFUL and PRIVATE website traffic system

(Yes, the phrase “going to put you in the freezer as punishment” was actually present in a spam comment I received over and over again for a while several years ago. It’s from a joke about a guy putting his pet parrot in the freezer. Look it up if you are really desperate to know.)

Similarly, I also have a small pile of fixed URLs and website names that get spammed over and over again for a period of time. I’m not going to list them here, since including them seems likely to get this site banned from search engine results. Besides, they usually only work as filters for a short period of time before the spammers move on to greener pastures.

On the other end of the spectrum, we have common and widely-used phrases that happen to occur frequently in spam from many different sources while not being likely in legitimate comments relevant to the content on my blog. This is a particularly tricky category, since these phrases could easily occur in genuine comments if the subject matter of my blog strays too far into certain territory. Because of this, I only have two phrases of this sort blacklisted at the moment:

search engine optimization where to buy

The largest category of filtered strings that I have at the moment is types and brand names of products that the spam purports to offer for sale at cheap prices. This is another category where a level of care is required, because it would be easy to accidentally filter out a legitimate comment that just happens to mention one of these items. Here is the list I am currently using:

air jordan auto insurance babyliss burberry canada goose gucci handbag hermes high heel jerseys jimmy choo jordans lacoste louboutin louis vuitton lululemon marc jacobs michael kor michaelkor mlb jersey moncler nail art nba jersey newbalance nfl jersey nike oakley sunglasses payday loan prada ray ban uggs wholesale beads

And last but certainly not least, we have the vices. These should be reasonably safe to filter out as long as my blog doesn’t get too, uh, *spicy*.

casino erotic porn sexy

And there you have it. A few simple word filters can catch the majority of the spam comments this blog receives. Not bad for what it is.

]]>I discovered this bug when I wrote some code that compiled in Eclipse, committed it, and then got an email a few minutes later from our Jenkins continuous integration server saying that the build failed. From the error message, I managed to track it down to a specific section of code that compiled in Eclipse but gave a compile error in javac.

This isn’t the first time I’ve ran into a Java compiler or standard library bug while developing CertSAFE, nor is it the first time that I’ve submitted a bug report via the Oracle web form. However, it is the first time that I’ve had a report accepted and published as a verified OpenJDK bug.

I’m always happy when I find a compiler bug, because it makes me feel better about bugs in my code to know that the platform developers screw up too.

]]>data MergeableSet = ... type Elem = Int empty :: (Elem, Elem) -> MergeableSet singleton :: (Elem, Elem) -> Elem -> MergeableSet size :: MergeableSet -> Int toList :: MergeableSet -> [Elem] union :: MergeableSet -> MergeableSet -> MergeableSet

Seems fairly reasonable, right? I’m going to show that **it is likely that no such data structure exists**.

First, note that some very similar data structures do in fact exist. Haskell’s Data.Set can be used to implement this interface with \(O(1)\) `singleton`

and `size`

, \(O(\log(n))\) membership testing (which is obviously much more powerful than `toList`

), and \(O(n)\) `union`

. Brodal, Makris, and Tsichlas (2006) presented a purely functional data structure that has \(O(1)\) `singleton`

, \(O(\log(n))\) membership testing, and \(O(1)\) “`join`

“, which is the same as `union`

but requires every element in the first set to be strictly less than every element in the second set.

So why is the variant above so implausible?

If a `MergeableSet`

data structure with the given time bounds exists (even without the `size`

operation), then **it is possible to find the transitive closure of an \(n\)-vertex graph in near-optimal time \(O(n^2 \log(n)^c)\)**.

The algorithms for computing the transitive closure of a graph with the current best known worst-case runtime are based on algorithms for fast matrix multiplication. In particular, transitive closure of a \(n\)-vertex graph can be computed in time \(O(n^\omega)\) where \(\omega < 2.373\) is the best known exponent for matrix multiplication. A faster algorithm for transitive closure would actually give a faster algorithm for Boolean matrix multiplication as well, as noted by Fischer and Meyer (1971).

Now, the problem of finding the transitive closures of a general graph can be reduced to the problem of finding the transitive closure of a directed acyclic graph. We can just compute the strongly connected components of the graph using any of the several linear-time algorithms, then compute the transitive closure of the resulting kernel DAG. Looping over the pairs of vertices in the original graph to move back to the starting domain takes \(O(n^2)\) time, but since the size of the output is \(n^2\) bits anyway there’s no additional asymptotic cost.

Suppose then that `MergeableSet`

exists and we want to find the transitive closure of a DAG. We can associate to each vertex the set of vertices reachable from that vertex, stored as a `MergeableSet`

. By traversing the graph in reverse topological order and using `union`

to combine the sets of all of the vertices adjacent to each vertex, we can compute `MergeableSet`

s of reachable vertices for all vertices in \(O(n^2 \log(n)^d)\) time. Then we just loop over all \(n\) vertices and obtain their lists of reachable vertices using `toList`

, which also takes \(O(n^2 \log(n)^d)\) time. A Haskell implementation of this idea (adding a slight \(O(\log(n))\) overhead by using `Data.Map`

so that I don’t have to get mutable arrays involved) looks like this:

import qualified Data.Array as Array import Data.Graph import qualified Data.Map as Map dagTransitiveClosure :: Graph -> Graph dagTransitiveClosure g = buildG (Array.bounds g) transitiveClosureEdges where rs = reachableSets g transitiveClosureEdges = [(v1, v2) | v1 <- vertices g, v2 <- toList (rs Map.! v1), v1 /= v2] type ReachableSets = Map.Map Vertex MergeableSet reachableSets :: Graph -> ReachableSets reachableSets g = foldl addVertex Map.empty $ topSort $ transposeG g where addVertex :: ReachableSets -> Vertex -> ReachableSets addVertex rs v = Map.insert v reachableSet rs where reachableSet = foldl union (singleton (Array.bounds g) v) $ map (rs Map.!) $ g Array.! v

If a `MergeableSet`

data structure with the given time bounds exists (even without the `toList`

operation), then **Cnf-Sat, the Boolean satisfiability problem for formulas in conjunctive normal form, has a \(2^{\delta n} \cdot \text{poly}(m)\) algorithm for some \(\delta < 1\)**.

Pătrașcu and Williams (2010) gave several hypotheses under which Cnf-Sat would have substantially faster algorithms than brute-force search. One of their theorems is as follows: if a certain problem 2Sat+2Clauses can be solved in time \(O((n + m)^{2 – \epsilon})\) for any \(\epsilon > 0\), then Cnf-Sat with \(n\) variables and \(m\) clauses can be solved in time \(2^{\delta n} \cdot \text{poly}(m)\) for some \(\delta < 1\). They note in passing that 2Sat+2Clauses reduces in linear time to the following problem:

Given a directed graph \(G = (V, E)\) and subsets \(S, T \subseteq V\), determine if there is some \(s \in S\) and \(t \in T\) with no path from \(s\) to \(t\).

By computing the strongly-connected components of \(G\), we can again without loss of generality assume that \(G\) is acyclic.

Now suppose that `MergeableSet`

exists. Then it is possible to solve this problem in time \(O((n + m) \cdot \log(n)^c)\) for a graph with \(n\) vertices and \(m\) edges. First, we compute the set of vertices in \(T\) reachable from each vertex, using essentially the same algorithm as the one for transitive closure from before. Then we loop over each vertex in \(S\) and use `size`

to test whether the size of its reachable set is less than \(|T|\). If we find a vertex \(s\) where this is the case, then return true; otherwise, return false. (We can also find a specific vertex \(t\) with no path from \(s\) to \(t\) by depth-first search from \(s\).)

So, to summarize, `MergeableSet`

would dramatically improve upon the known upper bounds for graph reachability problems. It’s probably too good to be true.

**Source code and documentation for rulesgen are available on GitHub**.